diff options
987 files changed, 46156 insertions, 8698 deletions
diff --git a/androidprefs/Android.mk b/androidprefs/Android.mk index 363b085..08fd10d 100644 --- a/androidprefs/Android.mk +++ b/androidprefs/Android.mk @@ -13,5 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # -JARUTILS_LOCAL_DIR := $(call my-dir) -include $(JARUTILS_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_MODULE := androidprefs + +include $(BUILD_HOST_JAVA_LIBRARY) + diff --git a/androidprefs/NOTICE b/androidprefs/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/androidprefs/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/anttasks/.classpath b/anttasks/.classpath index 08ced21..9fc1c9c 100644 --- a/anttasks/.classpath +++ b/anttasks/.classpath @@ -4,5 +4,6 @@ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/ant/ant.jar"/> + <classpathentry combineaccessrules="false" kind="src" path="/common"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/anttasks/Android.mk b/anttasks/Android.mk index 15ee903..e75a7cd 100644 --- a/anttasks/Android.mk +++ b/anttasks/Android.mk @@ -13,5 +13,19 @@ # See the License for the specific language governing permissions and # limitations under the License. # -ANTTASKS_LOCAL_DIR := $(call my-dir) -include $(ANTTASKS_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +LOCAL_JAVA_LIBRARIES := \ + sdklib \ + ant + +LOCAL_MODULE := anttasks + +include $(BUILD_HOST_JAVA_LIBRARY) + diff --git a/anttasks/NOTICE b/anttasks/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/anttasks/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/anttasks/etc/manifest.txt b/anttasks/etc/manifest.txt index 0d89364..257f2aa 100644 --- a/anttasks/etc/manifest.txt +++ b/anttasks/etc/manifest.txt @@ -1 +1 @@ -Class-Path: sdklib.jar +Class-Path: common.jar sdklib.jar diff --git a/anttasks/src/com/android/ant/SetupTask.java b/anttasks/src/com/android/ant/SetupTask.java index 6dc2c0f..e15f77b 100644 --- a/anttasks/src/com/android/ant/SetupTask.java +++ b/anttasks/src/com/android/ant/SetupTask.java @@ -16,6 +16,8 @@ package com.android.ant; +import com.android.io.FileWrapper; +import com.android.io.FolderWrapper; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; @@ -24,8 +26,6 @@ import com.android.sdklib.SdkManager; import com.android.sdklib.IAndroidTarget.IOptionalLibrary; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectProperties.PropertyType; -import com.android.sdklib.io.FileWrapper; -import com.android.sdklib.io.FolderWrapper; import com.android.sdklib.xml.AndroidManifest; import com.android.sdklib.xml.AndroidXPathFactory; diff --git a/apkbuilder/NOTICE b/apkbuilder/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/apkbuilder/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/archquery/Android.mk b/archquery/Android.mk index 53cad46..8936178 100644 --- a/archquery/Android.mk +++ b/archquery/Android.mk @@ -13,5 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. # -ARCHQUERY_LOCAL_DIR := $(call my-dir) -include $(ARCHQUERY_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +LOCAL_JAVA_LIBRARIES := \ + +LOCAL_MODULE := archquery + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/archquery/NOTICE b/archquery/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/archquery/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/build/patch_windows_sdk.sh b/build/patch_windows_sdk.sh index 2bbf24c..b01e41e 100755 --- a/build/patch_windows_sdk.sh +++ b/build/patch_windows_sdk.sh @@ -22,7 +22,8 @@ TOPDIR=${TOPDIR:-$3} # Remove obsolete stuff from tools TOOLS=$TEMP_SDK_DIR/tools LIB=$TEMP_SDK_DIR/tools/lib -rm $V $TOOLS/{android,apkbuilder,ddms,draw9patch,emulator} +rm $V $TOOLS/{android,apkbuilder,ddms,draw9patch} +rm $V $TOOLS/{emulator,emulator-arm,emulator-x86} rm $V $TOOLS/{hierarchyviewer,layoutopt,mksdcard,traceview,monkeyrunner} rm $V $TOOLS/proguard/bin/*.sh diff --git a/build/tools.atree b/build/tools.atree index d40d8e3..2e566e6 100644 --- a/build/tools.atree +++ b/build/tools.atree @@ -40,8 +40,12 @@ bin/mksdcard tools/mksdcard bin/zipalign tools/zipalign # emulator -bin/emulator tools/emulator -sdk/emulator/snapshot/snapshots.img tools/lib/emulator/snapshots.img +bin/emulator tools/emulator +bin/emulator-x86 tools/emulator-x86 +bin/emulator-arm tools/emulator-arm +sdk/emulator/snapshot/snapshots.img tools/lib/emulator/snapshots.img +usr/share/pc-bios/bios.bin tools/lib/pc-bios/bios.bin +usr/share/pc-bios/vgabios-cirrus.bin tools/lib/pc-bios/vgabios-cirrus.bin # Java-Based SDK Tools bin/ddms tools/ddms @@ -74,6 +78,7 @@ sdk/files/android.el tools/lib/android.el # Java Libraries for the tools framework/androidprefs.jar tools/lib/androidprefs.jar framework/common.jar tools/lib/common.jar +framework/swtmenubar.jar tools/lib/swtmenubar.jar sdk/apkbuilder/etc/apkbuilder tools/apkbuilder framework/sdkstats.jar tools/lib/sdkstats.jar framework/archquery.jar tools/lib/archquery.jar @@ -127,3 +132,8 @@ external/proguard/src/proguard/ant/task.properties tools/proguard/ant ############################################################################## sdk/testapps tests/testapps +framework/ddmlib-tests.jar tests/libtests/ddmlib-tests.jar +framework/ninepatch-tests.jar tests/libtests/ninepatch-tests.jar +framework/common-tests.jar tests/libtests/common-tests.jar +framework/sdklib-tests.jar tests/libtests/sdklib-tests.jar +framework/sdkuilib-tests.jar tests/libtests/sdkuilib-tests.jar diff --git a/build/windows_sdk_tools.mk b/build/windows_sdk_tools.mk index 8164cc5..73f4d12 100644 --- a/build/windows_sdk_tools.mk +++ b/build/windows_sdk_tools.mk @@ -5,6 +5,8 @@ WIN_SDK_TARGETS := \ emulator \ + emulator-arm \ + emulator-x86 \ mksdcard \ sdklauncher diff --git a/changes.txt b/changes.txt index 970ba93..73bd929 100644 --- a/changes.txt +++ b/changes.txt @@ -1,5 +1,13 @@ Change log for Android SDK Tools. +Revision 11: +- See eclipse/changes.txt for ADT related changes. + +Revision 10: +- The tools now automatically generate Java Programming Language + source files (in the gen directory) and bytecode (in the res/raw + directory) from your native .rs files + Revision 9: - Fix packaging issue that broke draw9patch - Ant build rules will now check the Ant version and fail if it's older than 1.8 diff --git a/common/Android.mk b/common/Android.mk index aa2ba8e..ef907f2 100644 --- a/common/Android.mk +++ b/common/Android.mk @@ -25,3 +25,6 @@ LOCAL_MODULE := common LOCAL_MODULE_TAGS := optional include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/common/NOTICE b/common/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/common/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/common/src/com/android/AndroidConstants.java b/common/src/com/android/AndroidConstants.java new file mode 100644 index 0000000..dfe137a --- /dev/null +++ b/common/src/com/android/AndroidConstants.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * Generic Android Constants. + */ +public class AndroidConstants { + + /** Default anim resource folder name, i.e. "anim" */ + public final static String FD_RES_ANIM = "anim"; //$NON-NLS-1$ + /** Default animator resource folder name, i.e. "animator" */ + public final static String FD_RES_ANIMATOR = "animator"; //$NON-NLS-1$ + /** Default color resource folder name, i.e. "color" */ + public final static String FD_RES_COLOR = "color"; //$NON-NLS-1$ + /** Default drawable resource folder name, i.e. "drawable" */ + public final static String FD_RES_DRAWABLE = "drawable"; //$NON-NLS-1$ + /** Default interpolator resource folder name, i.e. "interpolator" */ + public final static String FD_RES_INTERPOLATOR = "interpolator"; //$NON-NLS-1$ + /** Default layout resource folder name, i.e. "layout" */ + public final static String FD_RES_LAYOUT = "layout"; //$NON-NLS-1$ + /** Default menu resource folder name, i.e. "menu" */ + public final static String FD_RES_MENU = "menu"; //$NON-NLS-1$ + /** Default menu resource folder name, i.e. "mipmap" */ + public final static String FD_RES_MIPMAP = "mipmap"; //$NON-NLS-1$ + /** Default values resource folder name, i.e. "values" */ + public final static String FD_RES_VALUES = "values"; //$NON-NLS-1$ + /** Default xml resource folder name, i.e. "xml" */ + public final static String FD_RES_XML = "xml"; //$NON-NLS-1$ + /** Default raw resource folder name, i.e. "raw" */ + public final static String FD_RES_RAW = "raw"; //$NON-NLS-1$ + + /** Separator between the resource folder qualifier. */ + public final static String RES_QUALIFIER_SEP = "-"; //$NON-NLS-1$ + +} diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileWrapper.java b/common/src/com/android/io/FileWrapper.java index afc19b2..2859c0d 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FileWrapper.java +++ b/common/src/com/android/io/FileWrapper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.io; +package com.android.io; import java.io.File; @@ -137,6 +137,10 @@ public class FileWrapper extends File implements IAbstractFile { return isFile(); } + public long getModificationStamp() { + return lastModified(); + } + public IAbstractFolder getParentFolder() { String p = this.getParent(); if (p == null) { diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FolderWrapper.java b/common/src/com/android/io/FolderWrapper.java index 33e31c1..26ed9cf 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/FolderWrapper.java +++ b/common/src/com/android/io/FolderWrapper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.io; +package com.android.io; import java.io.File; diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IAbstractFile.java b/common/src/com/android/io/IAbstractFile.java index 2ff1fc8..6dfc8d8 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IAbstractFile.java +++ b/common/src/com/android/io/IAbstractFile.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.io; +package com.android.io; import java.io.InputStream; import java.io.OutputStream; @@ -50,4 +50,9 @@ public interface IAbstractFile extends IAbstractResource { * Returns the preferred mode to write into the file. */ PreferredWriteMode getPreferredWriteMode(); + + /** + * Returns the last modification timestamp + */ + long getModificationStamp(); } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IAbstractFolder.java b/common/src/com/android/io/IAbstractFolder.java index 17c0dbd..8335ef9 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IAbstractFolder.java +++ b/common/src/com/android/io/IAbstractFolder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.io; +package com.android.io; import java.io.File; diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IAbstractResource.java b/common/src/com/android/io/IAbstractResource.java index 0ccb107..3d762eb 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/IAbstractResource.java +++ b/common/src/com/android/io/IAbstractResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.io; +package com.android.io; /** * Base representation of a file system resource.<p/> diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/StreamException.java b/common/src/com/android/io/StreamException.java index 2088864..f67c7a8 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/io/StreamException.java +++ b/common/src/com/android/io/StreamException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.sdklib.io; +package com.android.io; /** * Exception thrown when {@link IAbstractFile#getContents()} fails. diff --git a/common/src/com/android/resources/FolderTypeRelationship.java b/common/src/com/android/resources/FolderTypeRelationship.java new file mode 100644 index 0000000..34961a3 --- /dev/null +++ b/common/src/com/android/resources/FolderTypeRelationship.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 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.resources; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class gives access to the bidirectional relationship between {@link ResourceType} and + * {@link ResourceFolderType}. + */ +public final class FolderTypeRelationship { + + private final static Map<ResourceType, List<ResourceFolderType>> mTypeToFolderMap = + new HashMap<ResourceType, List<ResourceFolderType>>(); + + private final static Map<ResourceFolderType, List<ResourceType>> mFolderToTypeMap = + new HashMap<ResourceFolderType, List<ResourceType>>(); + + static { + // generate the relationships in a temporary map + add(ResourceType.ANIM, ResourceFolderType.ANIM); + add(ResourceType.ANIMATOR, ResourceFolderType.ANIMATOR); + add(ResourceType.ARRAY, ResourceFolderType.VALUES); + add(ResourceType.ATTR, ResourceFolderType.VALUES); + add(ResourceType.BOOL, ResourceFolderType.VALUES); + add(ResourceType.COLOR, ResourceFolderType.VALUES); + add(ResourceType.COLOR, ResourceFolderType.COLOR); + add(ResourceType.DECLARE_STYLEABLE, ResourceFolderType.VALUES); + add(ResourceType.DIMEN, ResourceFolderType.VALUES); + add(ResourceType.DRAWABLE, ResourceFolderType.VALUES); + add(ResourceType.DRAWABLE, ResourceFolderType.DRAWABLE); + add(ResourceType.FRACTION, ResourceFolderType.VALUES); + add(ResourceType.ID, ResourceFolderType.VALUES); + add(ResourceType.INTEGER, ResourceFolderType.VALUES); + add(ResourceType.INTERPOLATOR, ResourceFolderType.INTERPOLATOR); + add(ResourceType.LAYOUT, ResourceFolderType.LAYOUT); + add(ResourceType.MENU, ResourceFolderType.MENU); + add(ResourceType.MIPMAP, ResourceFolderType.MIPMAP); + add(ResourceType.PLURALS, ResourceFolderType.VALUES); + add(ResourceType.PUBLIC, ResourceFolderType.VALUES); + add(ResourceType.RAW, ResourceFolderType.RAW); + add(ResourceType.STRING, ResourceFolderType.VALUES); + add(ResourceType.STYLE, ResourceFolderType.VALUES); + add(ResourceType.STYLEABLE, ResourceFolderType.VALUES); + add(ResourceType.XML, ResourceFolderType.XML); + + makeSafe(); + } + + /** + * Returns a list of {@link ResourceType}s that can be generated from files inside a folder + * of the specified type. + * @param folderType The folder type. + * @return a list of {@link ResourceType}, possibly empty but never null. + */ + public static List<ResourceType> getRelatedResourceTypes(ResourceFolderType folderType) { + List<ResourceType> list = mFolderToTypeMap.get(folderType); + if (list != null) { + return list; + } + + return Collections.emptyList(); + } + + /** + * Returns a list of {@link ResourceFolderType} that can contain files generating resources + * of the specified type. + * @param resType the type of resource. + * @return a list of {@link ResourceFolderType}, possibly empty but never null. + */ + public static List<ResourceFolderType> getRelatedFolders(ResourceType resType) { + List<ResourceFolderType> list = mTypeToFolderMap.get(resType); + if (list != null) { + return list; + } + + return Collections.emptyList(); + } + + /** + * Returns true if the {@link ResourceType} and the {@link ResourceFolderType} values match. + * @param resType the resource type. + * @param folderType the folder type. + * @return true if files inside the folder of the specified {@link ResourceFolderType} + * could generate a resource of the specified {@link ResourceType} + */ + public static boolean match(ResourceType resType, ResourceFolderType folderType) { + List<ResourceFolderType> list = mTypeToFolderMap.get(resType); + + if (list != null) { + return list.contains(folderType); + } + + return false; + } + + /** + * Adds a {@link ResourceType} - {@link ResourceFolderType} relationship. this indicates that + * a file in the folder can generate a resource of the specified type. + * @param type The resourceType + * @param folder The {@link ResourceFolderType} + */ + private static void add(ResourceType type, ResourceFolderType folder) { + // first we add the folder to the list associated with the type. + List<ResourceFolderType> folderList = mTypeToFolderMap.get(type); + if (folderList == null) { + folderList = new ArrayList<ResourceFolderType>(); + mTypeToFolderMap.put(type, folderList); + } + if (folderList.indexOf(folder) == -1) { + folderList.add(folder); + } + + // now we add the type to the list associated with the folder. + List<ResourceType> typeList = mFolderToTypeMap.get(folder); + if (typeList == null) { + typeList = new ArrayList<ResourceType>(); + mFolderToTypeMap.put(folder, typeList); + } + if (typeList.indexOf(type) == -1) { + typeList.add(type); + } + } + + /** + * Makes the maps safe by replacing the current list values with unmodifiable lists. + */ + private static void makeSafe() { + for (ResourceType type : ResourceType.values()) { + List<ResourceFolderType> list = mTypeToFolderMap.get(type); + if (list != null) { + // replace with a unmodifiable list wrapper around the current list. + mTypeToFolderMap.put(type, Collections.unmodifiableList(list)); + } + } + + for (ResourceFolderType folder : ResourceFolderType.values()) { + List<ResourceType> list = mFolderToTypeMap.get(folder); + if (list != null) { + // replace with a unmodifiable list wrapper around the current list. + mFolderToTypeMap.put(folder, Collections.unmodifiableList(list)); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java b/common/src/com/android/resources/ResourceFolderType.java index 91aec28..3a5b64d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolderType.java +++ b/common/src/com/android/resources/ResourceFolderType.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,26 +14,25 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.resources; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.sdklib.SdkConstants; +import com.android.AndroidConstants; /** * Enum representing a type of resource folder. */ public enum ResourceFolderType { - ANIM(SdkConstants.FD_ANIM), - ANIMATOR(SdkConstants.FD_ANIMATOR), - COLOR(SdkConstants.FD_COLOR), - DRAWABLE(SdkConstants.FD_DRAWABLE), - INTERPOLATOR(SdkConstants.FD_INTERPOLATOR), - LAYOUT(SdkConstants.FD_LAYOUT), - MENU(SdkConstants.FD_MENU), - MIPMAP(SdkConstants.FD_MIPMAP), - RAW(SdkConstants.FD_RAW), - VALUES(SdkConstants.FD_VALUES), - XML(SdkConstants.FD_XML); + ANIM(AndroidConstants.FD_RES_ANIM), + ANIMATOR(AndroidConstants.FD_RES_ANIMATOR), + COLOR(AndroidConstants.FD_RES_COLOR), + DRAWABLE(AndroidConstants.FD_RES_DRAWABLE), + INTERPOLATOR(AndroidConstants.FD_RES_INTERPOLATOR), + LAYOUT(AndroidConstants.FD_RES_LAYOUT), + MENU(AndroidConstants.FD_RES_MENU), + MIPMAP(AndroidConstants.FD_RES_MIPMAP), + RAW(AndroidConstants.FD_RES_RAW), + VALUES(AndroidConstants.FD_RES_VALUES), + XML(AndroidConstants.FD_RES_XML); private final String mName; @@ -71,7 +70,7 @@ public enum ResourceFolderType { */ public static ResourceFolderType getFolderType(String folderName) { // split the name of the folder in segments. - String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP); + String[] folderSegments = folderName.split(AndroidConstants.RES_QUALIFIER_SEP); // get the enum for the resource type. return getTypeByName(folderSegments[0]); diff --git a/common/src/com/android/resources/ResourceType.java b/common/src/com/android/resources/ResourceType.java index a4d3aa2..e9d4d53 100644 --- a/common/src/com/android/resources/ResourceType.java +++ b/common/src/com/android/resources/ResourceType.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/common/tests/.classpath b/common/tests/.classpath new file mode 100644 index 0000000..b793adc --- /dev/null +++ b/common/tests/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry combineaccessrules="false" kind="src" path="/common"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/common/tests/.project b/common/tests/.project new file mode 100644 index 0000000..9f550a3 --- /dev/null +++ b/common/tests/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>common-tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/archquery/src/Android.mk b/common/tests/Android.mk index 980f002..10e75e8 100644 --- a/archquery/src/Android.mk +++ b/common/tests/Android.mk @@ -1,5 +1,4 @@ -# -# Copyright (C) 2009 The Android Open Source Project +# 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. @@ -12,14 +11,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# + LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAR_MANIFEST := ../etc/manifest.txt -LOCAL_JAVA_LIBRARIES := \ +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_MODULE := common-tests +LOCAL_MODULE_TAGS := optional -LOCAL_MODULE := archquery +LOCAL_JAVA_LIBRARIES := common junit include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/FolderTypeRelationShipTest.java b/common/tests/src/com/android/resources/FolderTypeRelationShipTest.java index f1ce9d9..809eae7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/manager/FolderTypeRelationShipTest.java +++ b/common/tests/src/com/android/resources/FolderTypeRelationShipTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.resources; +import com.android.resources.FolderTypeRelationship; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import junit.framework.TestCase; @@ -27,7 +29,7 @@ public class FolderTypeRelationShipTest extends TestCase { // loop on all the enum, and make sure there's at least one folder type for it. for (ResourceType type : ResourceType.values()) { assertTrue(type.getDisplayName(), - FolderTypeRelationship.getRelatedFolders(type).length > 0); + FolderTypeRelationship.getRelatedFolders(type).size() > 0); } } @@ -36,8 +38,7 @@ public class FolderTypeRelationShipTest extends TestCase { // loop on all the enum, and make sure there's at least one res type for it. for (ResourceFolderType type : ResourceFolderType.values()) { assertTrue(type.getName(), - FolderTypeRelationship.getRelatedResourceTypes(type).length > 0); + FolderTypeRelationship.getRelatedResourceTypes(type).size() > 0); } } - } diff --git a/ddms/app/.classpath b/ddms/app/.classpath index 1040688..6caf176 100644 --- a/ddms/app/.classpath +++ b/ddms/app/.classpath @@ -1,12 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry excluding="Makefile|resources/" kind="src" path="src"/> - <classpathentry kind="src" path="src/resources"/> + <classpathentry kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_OSGI"/> <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/> <classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/> <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/> <classpathentry combineaccessrules="false" kind="src" path="/SdkStatsService"/> + <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/swtmenubar.jar" sourcepath="/ANDROID_SRC/sdk/swtmenubar/src"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/ddms/app/.settings/org.eclipse.jdt.core.prefs b/ddms/app/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/ddms/app/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/ddms/app/Android.mk b/ddms/app/Android.mk index 3857706..d1e4a52 100644 --- a/ddms/app/Android.mk +++ b/ddms/app/Android.mk @@ -1,5 +1,30 @@ # Copyright 2007 The Android Open Source Project # -DDMSAPP_LOCAL_DIR := $(call my-dir) -include $(DDMSAPP_LOCAL_DIR)/etc/Android.mk -include $(DDMSAPP_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +# If the dependency list is changed, etc/manifest.txt +# MUST be updated as well (Except for swt.jar which is dynamically +# added based on whether the VM is 32 or 64 bit) +LOCAL_JAVA_LIBRARIES := \ + androidprefs \ + sdkstats \ + ddmlib \ + ddmuilib \ + swt \ + swtmenubar \ + org.eclipse.jface_3.4.2.M20090107-0800 \ + org.eclipse.equinox.common_3.4.0.v20080421-2006 \ + org.eclipse.core.commands_3.4.0.I20080509-2000 +LOCAL_MODULE := ddms + +include $(BUILD_HOST_JAVA_LIBRARY) + + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/ddms/app/NOTICE b/ddms/app/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/ddms/app/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/ddms/app/README b/ddms/app/README index cc55ddd..42efb7b 100644 --- a/ddms/app/README +++ b/ddms/app/README @@ -1,11 +1,75 @@ -Using the Eclipse projects for ddms. +Using the Eclipse project DDMS +------------------------------ -ddms requires SWT to compile. +DDMS requires some external libraries to compile. +If you build DDMS using the makefile, you have nothing to configure. +However if you want to develop on DDMS using Eclipse, you need to +perform the following configuration. -SWT is available in the depot under //device/prebuild/<platform>/swt -Because the build path cannot contain relative path that are not inside the project directory, -the .classpath file references a user library called ANDROID_SWT. +------- +1- Projects required in Eclipse +------- -In order to compile the project, make a user library called ANDROID_SWT containing the jar -available at //device/prebuild/<platform>/swt.
\ No newline at end of file +To run DDMS from Eclipse, you need to import the following 5 projects: + + - sdk/androidpprefs: project AndroidPrefs + - sdk/sdkstats: project SdkStatsService + - sdk/ddms/app: project Ddms + - sdk/ddms/libs/ddmlib: project Ddmlib + - sdk/ddms/libs/ddmuilib: project Ddmuilib + + +------- +2- DDMS requires some SWT and OSGI JARs to compile. +------- + +SWT is available in the tree under prebuild/<platform>/swt + +Because the build path cannot contain relative path that are not inside +the project directory, the .classpath file references a user library +called ANDROID_SWT. +SWT depends on OSGI, so we'll also create an ANDROID_OSGI library for that. + +In order to compile the project: +- Open Preferences > Java > Build Path > User Libraries + +- Create a new user library named ANDROID_SWT +- Add the following 4 JAR files: + + - prebuilt/<platform>/swt/swt.jar + - prebuilt/common/eclipse/org.eclipse.core.commands_3.*.jar + - prebuilt/common/eclipse/org.eclipse.equinox.common_3.*.jar + - prebuilt/common/eclipse/org.eclipse.jface_3.*.jar + +- Create a new user library named ANDROID_OSGI +- Add the following JAR file: + + - prebuilt/common/osgi/osgi.jar + + +------- +3- DDMS also requires the compiled SwtMenuBar library. +------- + +Build the swtmenubar library: +$ cd $TOP (top of Android tree) +$ . build/envsetup.sh && lunch sdk-eng +$ sdk/eclipse/scripts/create_sdkman_symlinks.sh + +Define a classpath variable in Eclipse: +- Open Preferences > Java > Build Path > Classpath Variables +- Create a new classpath variable named ANDROID_OUT_FRAMEWORK +- Set its folder value to <Android tree>/out/host/<platform>/framework +- Create a new classpath variable named ANDROID_SRC +- Set its folder value to <Android tree> + +You might need to clean the ddms project (Project > Clean...) after +you add the new classpath variable, otherwise previous errors might not +go away automatically. + +The ANDROID_SRC part should be optional. It allows you to have access to +the SwtMenuBar generic parts from the Java editor. + +-- +EOF diff --git a/ddms/app/etc/ddms b/ddms/app/etc/ddms index 10b3a56..b0e529b 100755 --- a/ddms/app/etc/ddms +++ b/ddms/app/etc/ddms @@ -75,7 +75,7 @@ if [ `uname` = "Linux" ]; then export GDK_NATIVE_WINDOWS=true fi -jarpath="$frameworkdir/$jarfile" +jarpath="$frameworkdir/$jarfile:$frameworkdir/swtmenubar.jar" # Figure out the path to the swt.jar for the current architecture. # if ANDROID_SWT is defined, then just use this. @@ -100,6 +100,8 @@ if [ ! -d "$swtpath" ]; then exit 1 fi -# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored -# might need more memory, e.g. -Xmx128M -exec "$javaCmd" -Xmx256M $os_opts $java_debug -Dcom.android.ddms.bindir="$progdir" -classpath "$jarpath:$swtpath/swt.jar" com.android.ddms.Main "$@" +exec "$javaCmd" \ + -Xmx256M $os_opts $java_debug \ + -Dcom.android.ddms.bindir="$progdir" \ + -classpath "$jarpath:$swtpath/swt.jar" \ + com.android.ddms.Main "$@" diff --git a/ddms/app/etc/ddms.bat b/ddms/app/etc/ddms.bat index 0b02cb3..4b0675d 100755 --- a/ddms/app/etc/ddms.bat +++ b/ddms/app/etc/ddms.bat @@ -48,7 +48,7 @@ if debug NEQ "%1" goto NoDebug shift 1
:NoDebug
-set jarpath=%frameworkdir%%jarfile%
+set jarpath=%frameworkdir%%jarfile%;%frameworkdir%swtmenubar.jar
if not defined ANDROID_SWT goto QueryArch
set swt_path=%ANDROID_SWT%
diff --git a/ddms/app/etc/manifest.txt b/ddms/app/etc/manifest.txt index e30c193..8c6ab23 100644 --- a/ddms/app/etc/manifest.txt +++ b/ddms/app/etc/manifest.txt @@ -1,3 +1,3 @@ Main-Class: com.android.ddms.Main -Class-Path: androidprefs.jar sdkstats.jar ddmlib.jar ddmuilib.jar org.eclipse.jface_3.4.2.M20090107-0800.jar org.eclipse.equinox.common_3.4.0.v20080421-2006.jar org.eclipse.core.commands_3.4.0.I20080509-2000.jar jcommon-1.0.12.jar jfreechart-1.0.9.jar jfreechart-1.0.9-swt.jar osgi.jar +Class-Path: androidprefs.jar sdkstats.jar ddmlib.jar ddmuilib.jar swtmenubar.jar org.eclipse.jface_3.4.2.M20090107-0800.jar org.eclipse.equinox.common_3.4.0.v20080421-2006.jar org.eclipse.core.commands_3.4.0.I20080509-2000.jar jcommon-1.0.12.jar jfreechart-1.0.9.jar jfreechart-1.0.9-swt.jar osgi.jar diff --git a/ddms/app/src/Android.mk b/ddms/app/src/Android.mk deleted file mode 100644 index bac4030..0000000 --- a/ddms/app/src/Android.mk +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2007 The Android Open Source Project -# -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := resources - -LOCAL_JAR_MANIFEST := ../etc/manifest.txt - -# If the dependency list is changed, etc/manifest.txt -# MUST be updated as well (Except for swt.jar which is dynamically -# added based on whether the VM is 32 or 64 bit) -LOCAL_JAVA_LIBRARIES := \ - androidprefs \ - sdkstats \ - ddmlib \ - ddmuilib \ - swt \ - org.eclipse.jface_3.4.2.M20090107-0800 \ - org.eclipse.equinox.common_3.4.0.v20080421-2006 \ - org.eclipse.core.commands_3.4.0.I20080509-2000 -LOCAL_MODULE := ddms - -include $(BUILD_HOST_JAVA_LIBRARY) - diff --git a/ddms/app/src/com/android/ddms/AboutDialog.java b/ddms/app/src/com/android/ddms/AboutDialog.java index d9387c9..1d6bd89 100644 --- a/ddms/app/src/com/android/ddms/AboutDialog.java +++ b/ddms/app/src/com/android/ddms/AboutDialog.java @@ -64,7 +64,7 @@ public class AboutDialog extends Dialog { Shell shell = new Shell(parent, getStyle()); shell.setText("About..."); - logoImage = loadImage(shell, "ddms-logo.png"); //$NON-NLS-1$ + logoImage = loadImage(shell, "ddms-128.png"); //$NON-NLS-1$ createContents(shell); shell.pack(); diff --git a/ddms/app/src/com/android/ddms/PrefsDialog.java b/ddms/app/src/com/android/ddms/PrefsDialog.java index 418b8ba..c957a89 100644 --- a/ddms/app/src/com/android/ddms/PrefsDialog.java +++ b/ddms/app/src/com/android/ddms/PrefsDialog.java @@ -294,7 +294,11 @@ public final class PrefsDialog { dlg.setPreferenceStore(mPrefStore); // run it - dlg.open(); + try { + dlg.open(); + } catch (Throwable t) { + Log.e("ddms", t); + } // save prefs try { diff --git a/ddms/app/src/com/android/ddms/UIThread.java b/ddms/app/src/com/android/ddms/UIThread.java index 2dc1cf0..cb6786a 100644 --- a/ddms/app/src/com/android/ddms/UIThread.java +++ b/ddms/app/src/com/android/ddms/UIThread.java @@ -50,6 +50,10 @@ import com.android.ddmuilib.logcat.LogColors; import com.android.ddmuilib.logcat.LogFilter; import com.android.ddmuilib.logcat.LogPanel; import com.android.ddmuilib.logcat.LogPanel.ILogFilterStorageManager; +import com.android.menubar.IMenuBarCallback; +import com.android.menubar.IMenuBarEnhancer; +import com.android.menubar.IMenuBarEnhancer.MenuBarMode; +import com.android.menubar.MenuBarEnhancer; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; @@ -84,7 +88,6 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; -import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Sash; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TabFolder; @@ -101,6 +104,8 @@ import java.util.ArrayList; * when {@link IDevice} / {@link Client} selection changes. */ public class UIThread implements IUiSelectionListener, IClientChangeListener { + private static final String APP_NAME = "DDMS"; + /* * UI tab panel definitions. The constants here must match up with the array * indices in mPanels. PANEL_CLIENT_LIST is a "virtual" panel representing @@ -414,10 +419,10 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { /** * Create SWT objects and drive the user interface event loop. - * @param location location of the folder that contains ddms. + * @param ddmsParentLocation location of the folder that contains ddms. */ public void runUI(String ddmsParentLocation) { - Display.setAppName("ddms"); + Display.setAppName(APP_NAME); mDisplay = new Display(); final Shell shell = new Shell(mDisplay); @@ -425,7 +430,7 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { mDdmUiLibLoader = ImageLoader.getDdmUiLibLoader(); shell.setImage(ImageLoader.getLoader(this.getClass()).loadImage(mDisplay, - "ddms-icon.png", //$NON-NLS-1$ + "ddms-128.png", //$NON-NLS-1$ 100, 50, null)); Log.setLogOutput(new ILogOutput() { @@ -435,11 +440,11 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { // dialog box only run in UI thread.. mDisplay.asyncExec(new Runnable() { public void run() { - Shell shell = mDisplay.getActiveShell(); + Shell activeShell = mDisplay.getActiveShell(); if (logLevel == LogLevel.ERROR) { - MessageDialog.openError(shell, tag, message); + MessageDialog.openError(activeShell, tag, message); } else { - MessageDialog.openWarning(shell, tag, message); + MessageDialog.openWarning(activeShell, tag, message); } } }); @@ -556,20 +561,20 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { shell.addControlListener(new ControlListener() { public void controlMoved(ControlEvent e) { // get the new x/y - Rectangle rect = shell.getBounds(); + Rectangle controlBounds = shell.getBounds(); // store in pref file - PreferenceStore prefs = PrefsDialog.getStore(); - prefs.setValue(PrefsDialog.SHELL_X, rect.x); - prefs.setValue(PrefsDialog.SHELL_Y, rect.y); + PreferenceStore currentPrefs = PrefsDialog.getStore(); + currentPrefs.setValue(PrefsDialog.SHELL_X, controlBounds.x); + currentPrefs.setValue(PrefsDialog.SHELL_Y, controlBounds.y); } public void controlResized(ControlEvent e) { // get the new w/h - Rectangle rect = shell.getBounds(); + Rectangle controlBounds = shell.getBounds(); // store in pref file - PreferenceStore prefs = PrefsDialog.getStore(); - prefs.setValue(PrefsDialog.SHELL_WIDTH, rect.width); - prefs.setValue(PrefsDialog.SHELL_HEIGHT, rect.height); + PreferenceStore currentPrefs = PrefsDialog.getStore(); + currentPrefs.setValue(PrefsDialog.SHELL_WIDTH, controlBounds.width); + currentPrefs.setValue(PrefsDialog.SHELL_HEIGHT, controlBounds.height); } }); } @@ -624,41 +629,31 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { shell.addControlListener(new ControlListener() { public void controlMoved(ControlEvent e) { // get the new x/y - Rectangle rect = shell.getBounds(); + Rectangle controlBounds = shell.getBounds(); // store in pref file - PreferenceStore prefs = PrefsDialog.getStore(); - prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x); - prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y); + PreferenceStore currentPrefs = PrefsDialog.getStore(); + currentPrefs.setValue(PrefsDialog.EXPLORER_SHELL_X, controlBounds.x); + currentPrefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, controlBounds.y); } public void controlResized(ControlEvent e) { // get the new w/h - Rectangle rect = shell.getBounds(); + Rectangle controlBounds = shell.getBounds(); // store in pref file - PreferenceStore prefs = PrefsDialog.getStore(); - prefs.setValue(PrefsDialog.EXPLORER_SHELL_WIDTH, rect.width); - prefs.setValue(PrefsDialog.EXPLORER_SHELL_HEIGHT, rect.height); + PreferenceStore currentPrefs = PrefsDialog.getStore(); + currentPrefs.setValue(PrefsDialog.EXPLORER_SHELL_WIDTH, controlBounds.width); + currentPrefs.setValue(PrefsDialog.EXPLORER_SHELL_HEIGHT, controlBounds.height); } }); } /* - * Set the confirm-before-close dialog. TODO: enable/disable in prefs. TODO: - * is there any point in having this? + * Set the confirm-before-close dialog. */ private void setConfirmClose(final Shell shell) { - if (true) - return; - - shell.addListener(SWT.Close, new Listener() { - public void handleEvent(Event event) { - int style = SWT.APPLICATION_MODAL | SWT.YES | SWT.NO; - MessageBox msgBox = new MessageBox(shell, style); - msgBox.setText("Confirm..."); - msgBox.setMessage("Close DDM?"); - event.doit = (msgBox.open() == SWT.YES); - } - }); + // Note: there was some commented out code to display a confirmation box + // when closing. The feature seems unnecessary and the code was not being + // used, so it has been removed. } /* @@ -677,8 +672,6 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { actionItem.setText("&Actions"); MenuItem deviceItem = new MenuItem(menuBar, SWT.CASCADE); deviceItem.setText("&Device"); - MenuItem helpItem = new MenuItem(menuBar, SWT.CASCADE); - helpItem.setText("&Help"); // create top-level menus Menu fileMenu = new Menu(menuBar); @@ -689,22 +682,11 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { actionItem.setMenu(actionMenu); Menu deviceMenu = new Menu(menuBar); deviceItem.setMenu(deviceMenu); - Menu helpMenu = new Menu(menuBar); - helpItem.setMenu(helpMenu); MenuItem item; // create File menu items item = new MenuItem(fileMenu, SWT.NONE); - item.setText("&Preferences..."); - item.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - PrefsDialog.run(shell); - } - }); - - item = new MenuItem(fileMenu, SWT.NONE); item.setText("&Static Port Configuration..."); item.addSelectionListener(new SelectionAdapter() { @Override @@ -714,18 +696,36 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { } }); - new MenuItem(fileMenu, SWT.SEPARATOR); + IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenu(APP_NAME, fileMenu, + new IMenuBarCallback() { + public void printError(String format, Object... args) { + Log.e("DDMS Menu Bar", String.format(format, args)); + } - item = new MenuItem(fileMenu, SWT.NONE); - item.setText("E&xit\tCtrl-Q"); - item.setAccelerator('Q' | (Main.isMac() ? SWT.COMMAND : SWT.CONTROL)); - item.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - shell.close(); + public void onPreferencesMenuSelected() { + PrefsDialog.run(shell); + } + + public void onAboutMenuSelected() { + AboutDialog dlg = new AboutDialog(shell); + dlg.open(); } }); + if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) { + new MenuItem(fileMenu, SWT.SEPARATOR); + + item = new MenuItem(fileMenu, SWT.NONE); + item.setText("E&xit\tCtrl-Q"); + item.setAccelerator('Q' | (Main.isMac() ? SWT.COMMAND : SWT.CONTROL)); + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + shell.close(); + } + }); + } + // create edit menu items mCopyMenuItem = new MenuItem(editMenu, SWT.NONE); mCopyMenuItem.setText("&Copy\tCtrl-C"); @@ -900,32 +900,6 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { } }); - // create Help menu items - item = new MenuItem(helpMenu, SWT.NONE); - item.setText("&Contents..."); - item.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - int style = SWT.APPLICATION_MODAL | SWT.OK; - MessageBox msgBox = new MessageBox(shell, style); - msgBox.setText("Help!"); - msgBox.setMessage("Help wanted."); - msgBox.open(); - } - }); - - new MenuItem(helpMenu, SWT.SEPARATOR); - - item = new MenuItem(helpMenu, SWT.NONE); - item.setText("&About..."); - item.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - AboutDialog dlg = new AboutDialog(shell); - dlg.open(); - } - }); - // tell the shell to use this menu shell.setMenuBar(menuBar); } @@ -1007,7 +981,9 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { e.y = Math.max(Math.min(e.y, bottom), 100); if (e.y != sashRect.y) { sashData.top = new FormAttachment(0, e.y); - prefs.setValue(PREFERENCE_LOGSASH, e.y); + if (prefs != null) { + prefs.setValue(PREFERENCE_LOGSASH, e.y); + } panelArea.layout(); } } @@ -1199,7 +1175,9 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { e.x = Math.max(Math.min(e.x, right), minPanelWidth); if (e.x != sashRect.x) { sashData.left = new FormAttachment(0, e.x); - prefs.setValue(PREFERENCE_SASH, e.x); + if (prefs != null) { + prefs.setValue(PREFERENCE_SASH, e.x); + } comp.layout(); } } @@ -1282,13 +1260,13 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { @Override public void widgetSelected(SelectionEvent e) { // disable the other actions and record current index - for (int i = 0 ; i < mLogLevelActions.length; i++) { - ToolItemAction a = mLogLevelActions[i]; + for (int k = 0 ; k < mLogLevelActions.length; k++) { + ToolItemAction a = mLogLevelActions[k]; if (a == newAction) { a.setChecked(true); // set the log level - mLogPanel.setCurrentFilterLogLevel(i+2); + mLogPanel.setCurrentFilterLogLevel(k+2); } else { a.setChecked(false); } @@ -1508,9 +1486,19 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { deleteAction.item.setText("Delete"); //$NON-NLS-1$ } + ToolItemAction createNewFolderAction = new ToolItemAction(toolBar, SWT.PUSH); + createNewFolderAction.item.setToolTipText("New Folder"); + image = mDdmUiLibLoader.loadImage("add.png", mDisplay); //$NON-NLS-1$ + if (image != null) { + createNewFolderAction.item.setImage(image); + } else { + // this is for debugging purpose when the icon is missing + createNewFolderAction.item.setText("New Folder"); //$NON-NLS-1$ + } + // device explorer mExplorer = new DeviceExplorer(); - mExplorer.setActions(pushAction, pullAction, deleteAction); + mExplorer.setActions(pushAction, pullAction, deleteAction, createNewFolderAction); pullAction.item.addSelectionListener(new SelectionAdapter() { @Override @@ -1536,6 +1524,14 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { }); deleteAction.setEnabled(false); + createNewFolderAction.item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mExplorer.createNewFolderInSelection(); + } + }); + createNewFolderAction.setEnabled(false); + Composite parent = new Composite(mExplorerShell, SWT.NONE); parent.setLayoutData(new GridData(GridData.FILL_BOTH)); diff --git a/ddms/app/src/images/ddms-128.png b/ddms/app/src/images/ddms-128.png Binary files differnew file mode 100644 index 0000000..392a8f3 --- /dev/null +++ b/ddms/app/src/images/ddms-128.png diff --git a/ddms/app/src/resources/images/ddms-icon.png b/ddms/app/src/resources/images/ddms-icon.png Binary files differdeleted file mode 100644 index 6390a2d..0000000 --- a/ddms/app/src/resources/images/ddms-icon.png +++ /dev/null diff --git a/ddms/app/src/resources/images/ddms-logo.png b/ddms/app/src/resources/images/ddms-logo.png Binary files differdeleted file mode 100644 index 6390a2d..0000000 --- a/ddms/app/src/resources/images/ddms-logo.png +++ /dev/null diff --git a/ddms/libs/ddmlib/.settings/org.eclipse.jdt.core.prefs b/ddms/libs/ddmlib/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/ddms/libs/ddmlib/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/ddms/libs/ddmlib/Android.mk b/ddms/libs/ddmlib/Android.mk index 8e46edf..978ac19 100644 --- a/ddms/libs/ddmlib/Android.mk +++ b/ddms/libs/ddmlib/Android.mk @@ -18,6 +18,7 @@ include $(CLEAR_VARS) # Only compile source java files in this lib. LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src LOCAL_MODULE := ddmlib diff --git a/ddms/libs/ddmlib/NOTICE b/ddms/libs/ddmlib/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/ddms/libs/ddmlib/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java b/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java index e965ccd..f697cba 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/AndroidDebugBridge.java @@ -200,7 +200,7 @@ public final class AndroidDebugBridge { /** * Terminates the ddm library. This must be called upon application termination. */ - public static void terminate() { + public static synchronized void terminate() { // kill the monitoring services if (sThis != null && sThis.mDeviceMonitor != null) { sThis.mDeviceMonitor.stop(); @@ -211,6 +211,8 @@ public final class AndroidDebugBridge { if (monitorThread != null) { monitorThread.quit(); } + + sInitialized = false; } /** diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/FileListingService.java b/ddms/libs/ddmlib/src/com/android/ddmlib/FileListingService.java index bfeeea2..5ef5428 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/FileListingService.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/FileListingService.java @@ -389,7 +389,7 @@ public final class FileListingService { * Returns an escaped version of the entry name. * @param entryName */ - private String escape(String entryName) { + public static String escape(String entryName) { return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$ } } @@ -745,7 +745,7 @@ public final class FileListingService { try { // create the command - String command = "ls -l " + entry.getFullPath(); //$NON-NLS-1$ + String command = "ls -l " + entry.getFullEscapedPath(); //$NON-NLS-1$ // create the receiver object that will parse the result from ls LsReceiver receiver = new LsReceiver(entry, entryList, linkList); diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/SyncException.java b/ddms/libs/ddmlib/src/com/android/ddmlib/SyncException.java index 1e9b692..5f84f64 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/SyncException.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/SyncException.java @@ -46,6 +46,8 @@ public class SyncException extends CanceledException { FILE_READ_ERROR("Reading local file failed!"), /** attempting to push a directory. */ LOCAL_IS_DIRECTORY("Local path is a directory."), + /** attempting to push a non-existent file. */ + NO_LOCAL_FILE("Local path doesn't exist."), /** when the target path of a multi file push is a file. */ REMOTE_IS_FILE("Remote path is a file."), /** receiving too much data from the remove device at once */ diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java b/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java index f112a12..03a68aa 100644 --- a/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java +++ b/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java @@ -23,7 +23,6 @@ import com.android.ddmlib.utils.ArrayHelper; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -231,8 +230,6 @@ public final class SyncService { * if the unique entry is a folder, this should be a folder. * @param monitor The progress monitor. Cannot be null. * @throws SyncException - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. * @throws IOException * @throws TimeoutException * @@ -240,7 +237,7 @@ public final class SyncService { * @see #getNullProgressMonitor() */ public void pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor) - throws SyncException, FileNotFoundException, IOException, TimeoutException { + throws SyncException, IOException, TimeoutException { // first we check the destination is a directory and exists File f = new File(localPath); @@ -271,16 +268,15 @@ public final class SyncService { * @param localFilename The local destination. * @param monitor The progress monitor. Cannot be null. * - * @throws SyncException - * @throws IOException - * @throws FileNotFoundException - * @throws TimeoutException + * @throws IOException in case of an IO exception. + * @throws TimeoutException in case of a timeout reading responses from the device. + * @throws SyncException in case of a sync exception. * * @see FileListingService.FileEntry * @see #getNullProgressMonitor() */ public void pullFile(FileEntry remote, String localFilename, ISyncProgressMonitor monitor) - throws FileNotFoundException, IOException, SyncException, TimeoutException { + throws IOException, SyncException, TimeoutException { int total = remote.getSizeValue(); monitor.start(total); @@ -326,14 +322,12 @@ public final class SyncService { * @param local An array of loca files to push * @param remote the remote {@link FileEntry} representing a directory. * @param monitor The progress monitor. Cannot be null. - * @throws SyncException - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. - * @throws IOException - * @throws TimeoutException + * @throws SyncException if file could not be pushed + * @throws IOException in case of I/O error on the connection. + * @throws TimeoutException in case of a timeout reading responses from the device. */ public void push(String[] local, FileEntry remote, ISyncProgressMonitor monitor) - throws SyncException, FileNotFoundException, IOException, TimeoutException { + throws SyncException, IOException, TimeoutException { if (remote.isDirectory() == false) { throw new SyncException(SyncError.REMOTE_IS_FILE); } @@ -361,18 +355,15 @@ public final class SyncService { * @param remote The remote filepath. * @param monitor The progress monitor. Cannot be null. * - * @throws SyncException - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. - * @throws IOException - * @throws TimeoutException - * + * @throws SyncException if file could not be pushed + * @throws IOException in case of I/O error on the connection. + * @throws TimeoutException in case of a timeout reading responses from the device. */ public void pushFile(String local, String remote, ISyncProgressMonitor monitor) - throws SyncException, FileNotFoundException, IOException, TimeoutException { + throws SyncException, IOException, TimeoutException { File f = new File(local); if (f.exists() == false) { - throw new FileNotFoundException(); + throw new SyncException(SyncError.NO_LOCAL_FILE); } if (f.isDirectory()) { @@ -439,16 +430,13 @@ public final class SyncService { * @param fileListingService a FileListingService object to browse through remote directories. * @param monitor the progress monitor. Must be started already. * - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. - * @throws IOException - * @throws SyncException - * @throws TimeoutException + * @throws SyncException if file could not be pushed + * @throws IOException in case of I/O error on the connection. + * @throws TimeoutException in case of a timeout reading responses from the device. */ private void doPull(FileEntry[] entries, String localPath, FileListingService fileListingService, - ISyncProgressMonitor monitor) throws SyncException, FileNotFoundException, IOException, - TimeoutException { + ISyncProgressMonitor monitor) throws SyncException, IOException, TimeoutException { for (FileEntry e : entries) { // check if we're cancelled @@ -484,15 +472,12 @@ public final class SyncService { * @param remotePath the remote file (length max is 1024) * @param localPath the local destination * @param monitor the monitor. The monitor must be started already. - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. - * @throws IOException - * @throws SyncException - * @throws TimeoutException + * @throws SyncException if file could not be pushed + * @throws IOException in case of I/O error on the connection. + * @throws TimeoutException in case of a timeout reading responses from the device. */ private void doPullFile(String remotePath, String localPath, - ISyncProgressMonitor monitor) throws FileNotFoundException, IOException, SyncException, - TimeoutException { + ISyncProgressMonitor monitor) throws IOException, SyncException, TimeoutException { byte[] msg = null; byte[] pullResult = new byte[8]; @@ -581,14 +566,12 @@ public final class SyncService { * @param remotePath * @param monitor * - * @throws SyncException - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. - * @throws IOException - * @throws TimeoutException + * @throws SyncException if file could not be pushed + * @throws IOException in case of I/O error on the connection. + * @throws TimeoutException in case of a timeout reading responses from the device. */ private void doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor) - throws SyncException, FileNotFoundException, IOException, TimeoutException { + throws SyncException, IOException, TimeoutException { for (File f : fileArray) { // check if we're canceled if (monitor.isCanceled() == true) { @@ -618,15 +601,12 @@ public final class SyncService { * @param remotePath the remote file (length max is 1024) * @param monitor the monitor. The monitor must be started already. * - * @throws SyncException - * @throws FileNotFoundException if the file exists but is a directory, does not exist but - * cannot be created, or cannot be opened for any other reason. - * @throws IOException - * @throws TimeoutException + * @throws SyncException if file could not be pushed + * @throws IOException in case of I/O error on the connection. + * @throws TimeoutException in case of a timeout reading responses from the device. */ private void doPushFile(String localPath, String remotePath, - ISyncProgressMonitor monitor) throws SyncException, FileNotFoundException, IOException, - TimeoutException { + ISyncProgressMonitor monitor) throws SyncException, IOException, TimeoutException { FileInputStream fis = null; byte[] msg; @@ -712,7 +692,7 @@ public final class SyncService { * @param result the current adb result. Must contain both FAIL and the length of the message. * @param timeOut * @return - * @throws TimeoutException + * @throws TimeoutException in case of a timeout reading responses from the device. * @throws IOException */ private String readErrorMessage(byte[] result, final int timeOut) throws TimeoutException, @@ -739,7 +719,7 @@ public final class SyncService { * @return an Integer containing the mode if all went well or null * otherwise * @throws IOException - * @throws TimeoutException + * @throws TimeoutException in case of a timeout reading responses from the device. */ private Integer readMode(String path) throws TimeoutException, IOException { // create the stat request message. diff --git a/ddms/libs/ddmuilib/.classpath b/ddms/libs/ddmuilib/.classpath index 2cd368c..839e2be 100644 --- a/ddms/libs/ddmuilib/.classpath +++ b/ddms/libs/ddmuilib/.classpath @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry excluding="Makefile|resources/" kind="src" path="src"/> - <classpathentry kind="src" path="src/resources"/> + <classpathentry kind="src" path="src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/> <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/> diff --git a/ddms/libs/ddmuilib/.settings/org.eclipse.jdt.core.prefs b/ddms/libs/ddmuilib/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/ddms/libs/ddmuilib/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/ddms/libs/ddmuilib/Android.mk b/ddms/libs/ddmuilib/Android.mk index 7059e5e..6b50f82 100644 --- a/ddms/libs/ddmuilib/Android.mk +++ b/ddms/libs/ddmuilib/Android.mk @@ -1,4 +1,28 @@ # Copyright 2007 The Android Open Source Project # -DDMUILIB_LOCAL_DIR := $(call my-dir) -include $(DDMUILIB_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +# If the dependency list is changed, etc/manifest.txt +# MUST be updated as well (Except for swt.jar which is dynamically +# added based on whether the VM is 32 or 64 bit) +LOCAL_JAVA_LIBRARIES := \ + ddmlib \ + swt \ + org.eclipse.jface_3.4.2.M20090107-0800 \ + org.eclipse.equinox.common_3.4.0.v20080421-2006 \ + org.eclipse.core.commands_3.4.0.I20080509-2000 \ + jcommon-1.0.12 \ + jfreechart-1.0.9 \ + jfreechart-1.0.9-swt + +LOCAL_MODULE := ddmuilib + +include $(BUILD_HOST_JAVA_LIBRARY) + diff --git a/ddms/libs/ddmuilib/NOTICE b/ddms/libs/ddmuilib/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/ddms/libs/ddmuilib/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/ddms/libs/ddmuilib/src/Android.mk b/ddms/libs/ddmuilib/src/Android.mk deleted file mode 100644 index cfc1791..0000000 --- a/ddms/libs/ddmuilib/src/Android.mk +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2007 The Android Open Source Project -# -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := resources - -LOCAL_JAR_MANIFEST := ../etc/manifest.txt - -# If the dependency list is changed, etc/manifest.txt -# MUST be updated as well (Except for swt.jar which is dynamically -# added based on whether the VM is 32 or 64 bit) -LOCAL_JAVA_LIBRARIES := \ - ddmlib \ - swt \ - org.eclipse.jface_3.4.2.M20090107-0800 \ - org.eclipse.equinox.common_3.4.0.v20080421-2006 \ - org.eclipse.core.commands_3.4.0.I20080509-2000 \ - jcommon-1.0.12 \ - jfreechart-1.0.9 \ - jfreechart-1.0.9-swt - -LOCAL_MODULE := ddmuilib - -include $(BUILD_HOST_JAVA_LIBRARY) - diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/ImageLoader.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/ImageLoader.java index 40cbd1d..fd480f6 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/ImageLoader.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/ImageLoader.java @@ -140,13 +140,11 @@ public class ImageLoader { if (imageStream != null) { img = new Image(display, imageStream); - if (img == null) { - throw new NullPointerException("couldn't load " + tmp); - } - mLoadedImages.put(filename, img); + } - return img; + if (img == null) { + throw new RuntimeException("Failed to load " + tmp); } } @@ -159,7 +157,6 @@ public class ImageLoader { * Extra parameters allows for creation of a replacement image of the * loading failed. * - * @param loader the image loader used. * @param display the Display object * @param fileName the file name * @param width optional width to create replacement Image. If -1, null be diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/ScreenShotDialog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/ScreenShotDialog.java index 590a8ab..d0c8a2f 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/ScreenShotDialog.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/ScreenShotDialog.java @@ -43,6 +43,7 @@ import org.eclipse.swt.widgets.Shell; import java.io.File; import java.io.IOException; +import java.util.Calendar; /** @@ -57,6 +58,8 @@ public class ScreenShotDialog extends Dialog { private RawImage mRawImage; private Clipboard mClipboard; + /** Number of 90 degree rotations applied to the current image */ + private int mRotateCount = 0; /** * Create with default style. @@ -118,6 +121,14 @@ public class ScreenShotDialog extends Dialog { @Override public void widgetSelected(SelectionEvent e) { updateDeviceImage(shell); + // RawImage only allows us to rotate the image 90 degrees at the time, + // so to preserve the current rotation we must call getRotated() + // the same number of times the user has done it manually. + // TODO: improve the RawImage class. + for (int i=0; i < mRotateCount; i++) { + mRawImage = mRawImage.getRotated(); + } + updateImageDisplay(shell); } }); @@ -131,6 +142,7 @@ public class ScreenShotDialog extends Dialog { @Override public void widgetSelected(SelectionEvent e) { if (mRawImage != null) { + mRotateCount = (mRotateCount + 1) % 4; mRawImage = mRawImage.getRotated(); updateImageDisplay(shell); } @@ -282,10 +294,13 @@ public class ScreenShotDialog extends Dialog { */ private void saveImage(Shell shell) { FileDialog dlg = new FileDialog(shell, SWT.SAVE); - String fileName; + + Calendar now = Calendar.getInstance(); + String fileName = String.format("device-%tF-%tH%tM%tS.png", + now, now, now, now); dlg.setText("Save image..."); - dlg.setFileName("device.png"); + dlg.setFileName(fileName); String lastDir = DdmUiPreferences.getStore().getString("lastImageSaveDir"); if (lastDir.length() == 0) { diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java index a0febeb..a466be1 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/explorer/DeviceExplorer.java @@ -19,23 +19,28 @@ package com.android.ddmuilib.explorer; import com.android.ddmlib.AdbCommandRejectedException; import com.android.ddmlib.DdmConstants; import com.android.ddmlib.FileListingService; +import com.android.ddmlib.FileListingService.FileEntry; import com.android.ddmlib.IDevice; import com.android.ddmlib.IShellOutputReceiver; import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.SyncException; import com.android.ddmlib.SyncService; -import com.android.ddmlib.TimeoutException; -import com.android.ddmlib.FileListingService.FileEntry; import com.android.ddmlib.SyncService.ISyncProgressMonitor; +import com.android.ddmlib.TimeoutException; import com.android.ddmuilib.DdmUiPreferences; import com.android.ddmuilib.ImageLoader; import com.android.ddmuilib.Panel; import com.android.ddmuilib.SyncProgressHelper; -import com.android.ddmuilib.TableHelper; import com.android.ddmuilib.SyncProgressHelper.SyncRunnable; +import com.android.ddmuilib.TableHelper; import com.android.ddmuilib.actions.ICommonAction; import com.android.ddmuilib.console.DdmConsole; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; @@ -96,6 +101,7 @@ public class DeviceExplorer extends Panel { private ICommonAction mPushAction; private ICommonAction mPullAction; private ICommonAction mDeleteAction; + private ICommonAction mCreateNewFolderAction; private Image mFileImage; private Image mFolderImage; @@ -134,12 +140,14 @@ public class DeviceExplorer extends Panel { * @param pushAction * @param pullAction * @param deleteAction + * @param createNewFolderAction */ public void setActions(ICommonAction pushAction, ICommonAction pullAction, - ICommonAction deleteAction) { + ICommonAction deleteAction, ICommonAction createNewFolderAction) { mPushAction = pushAction; mPullAction = pullAction; mDeleteAction = deleteAction; + mCreateNewFolderAction = createNewFolderAction; } /** @@ -201,6 +209,7 @@ public class DeviceExplorer extends Panel { mPullAction.setEnabled(false); mPushAction.setEnabled(false); mDeleteAction.setEnabled(false); + mCreateNewFolderAction.setEnabled(false); return; } if (sel instanceof IStructuredSelection) { @@ -212,7 +221,9 @@ public class DeviceExplorer extends Panel { mPullAction.setEnabled(true); mPushAction.setEnabled(selection.size() == 1); if (selection.size() == 1) { - setDeleteEnabledState((FileEntry)element); + FileEntry entry = (FileEntry) element; + setDeleteEnabledState(entry); + mCreateNewFolderAction.setEnabled(entry.isDirectory()); } else { mDeleteAction.setEnabled(false); } @@ -617,6 +628,82 @@ public class DeviceExplorer extends Panel { } + public void createNewFolderInSelection() { + TreeItem[] items = mTree.getSelection(); + + if (items.length != 1) { + return; + } + + final FileEntry entry = (FileEntry) items[0].getData(); + + if (entry.isDirectory()) { + InputDialog inputDialog = new InputDialog(mTree.getShell(), "New Folder", + "Please enter the new folder name", "New Folder", new IInputValidator() { + public String isValid(String newText) { + if ((newText != null) && (newText.length() > 0) + && (newText.trim().length() > 0) + && (newText.indexOf('/') == -1) + && (newText.indexOf('\\') == -1)) { + return null; + } else { + return "Invalid name"; + } + } + }); + inputDialog.open(); + String value = inputDialog.getValue(); + + if (value != null) { + // create the mkdir command + String command = "mkdir " + entry.getFullEscapedPath() //$NON-NLS-1$ + + FileListingService.FILE_SEPARATOR + FileEntry.escape(value); + + try { + mCurrentDevice.executeShellCommand(command, new IShellOutputReceiver() { + + public boolean isCancelled() { + return false; + } + + public void flush() { + mTreeViewer.refresh(entry); + } + + public void addOutput(byte[] data, int offset, int length) { + String errorMessage; + if (data != null) { + errorMessage = new String(data); + } else { + errorMessage = ""; + } + Status status = new Status(IStatus.ERROR, + "DeviceExplorer", 0, errorMessage, null); //$NON-NLS-1$ + ErrorDialog.openError(mTree.getShell(), "New Folder Error", + "New Folder Error", status); + } + }); + } catch (TimeoutException e) { + // adb failed somehow, we do nothing. We should be + // displaying the error from the output of the shell + // command. + } catch (AdbCommandRejectedException e) { + // adb failed somehow, we do nothing. We should be + // displaying the error from the output of the shell + // command. + } catch (ShellCommandUnresponsiveException e) { + // adb failed somehow, we do nothing. We should be + // displaying the error from the output of the shell + // command. + } catch (IOException e) { + // adb failed somehow, we do nothing. We should be + // displaying the error from the output of the shell + // command. + } + } + } + } + /** * Force a full refresh of the explorer. */ diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java index f6e2f19..10680f7 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java @@ -17,14 +17,14 @@ package com.android.ddmuilib.handler; import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData.IMethodProfilingHandler; import com.android.ddmlib.DdmConstants; import com.android.ddmlib.IDevice; import com.android.ddmlib.Log; import com.android.ddmlib.SyncException; import com.android.ddmlib.SyncService; -import com.android.ddmlib.TimeoutException; -import com.android.ddmlib.ClientData.IMethodProfilingHandler; import com.android.ddmlib.SyncService.ISyncProgressMonitor; +import com.android.ddmlib.TimeoutException; import com.android.ddmuilib.DdmUiPreferences; import com.android.ddmuilib.SyncProgressHelper; import com.android.ddmuilib.SyncProgressHelper.SyncRunnable; @@ -126,7 +126,7 @@ public class MethodProfilingHandler extends BaseFileHandler SyncProgressHelper.run(new SyncRunnable() { public void run(ISyncProgressMonitor monitor) throws SyncException, IOException, TimeoutException { - sync.pullFile(tempPath, remoteFilePath, monitor); + sync.pullFile(remoteFilePath, tempPath, monitor); } public void close() { diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/EditFilterDialog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/EditFilterDialog.java index 8bbc7c5..6cd44d0 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/EditFilterDialog.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/EditFilterDialog.java @@ -43,7 +43,10 @@ import org.eclipse.swt.widgets.Text; public class EditFilterDialog extends Dialog { private static final int DLG_WIDTH = 400; - private static final int DLG_HEIGHT = 250; + private static final int DLG_HEIGHT = 260; + + private static final String IMAGE_WARNING = "warning.png"; //$NON-NLS-1$ + private static final String IMAGE_EMPTY = "empty.png"; //$NON-NLS-1$ private Shell mParent; @@ -68,6 +71,8 @@ public class EditFilterDialog extends Dialog { private Button mOkButton; + private Label mNameWarning; + private Label mTagWarning; private Label mPidWarning; public EditFilterDialog(Shell parent) { @@ -151,7 +156,7 @@ public class EditFilterDialog extends Dialog { // top part with the filter name Composite nameComposite = new Composite(mShell, SWT.NONE); nameComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); - nameComposite.setLayout(new GridLayout(2, false)); + nameComposite.setLayout(new GridLayout(3, false)); Label l = new Label(nameComposite, SWT.NONE); l.setText("Filter Name:"); @@ -172,6 +177,10 @@ public class EditFilterDialog extends Dialog { } }); + mNameWarning = new Label(nameComposite, SWT.NONE); + mNameWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_EMPTY, + mShell.getDisplay())); + // separator l = new Label(mShell, SWT.SEPARATOR | SWT.HORIZONTAL); l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); @@ -192,9 +201,8 @@ public class EditFilterDialog extends Dialog { tagText.setText(mTag); } } - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - tagText.setLayoutData(gd); + + tagText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); tagText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { mTag = tagText.getText().trim(); @@ -202,6 +210,10 @@ public class EditFilterDialog extends Dialog { } }); + mTagWarning = new Label(main, SWT.NONE); + mTagWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_EMPTY, + mShell.getDisplay())); + l = new Label(main, SWT.NONE); l.setText("by pid:"); @@ -223,14 +235,14 @@ public class EditFilterDialog extends Dialog { }); mPidWarning = new Label(main, SWT.NONE); - mPidWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage("empty.png", //$NON-NLS-1$ + mPidWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_EMPTY, mShell.getDisplay())); l = new Label(main, SWT.NONE); l.setText("by Log level:"); final Combo logCombo = new Combo(main, SWT.DROP_DOWN | SWT.READ_ONLY); - gd = new GridData(GridData.FILL_HORIZONTAL); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); gd.horizontalSpan = 2; logCombo.setLayoutData(gd); @@ -324,26 +336,58 @@ public class EditFilterDialog extends Dialog { */ private void validate() { + boolean result = true; + // then we check it only contains digits. if (mPid != null) { if (mPid.matches("[0-9]*") == false) { //$NON-NLS-1$ - mOkButton.setEnabled(false); mPidWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage( - "warning.png", //$NON-NLS-1$ + IMAGE_WARNING, mShell.getDisplay())); - return; + mPidWarning.setToolTipText("PID must be a number"); //$NON-NLS-1$ + result = false; } else { mPidWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage( - "empty.png", //$NON-NLS-1$ + IMAGE_EMPTY, mShell.getDisplay())); + mPidWarning.setToolTipText(null); } } - if (mName == null || mName.length() == 0) { - mOkButton.setEnabled(false); - return; + // then we check it not contains character | or : + if (mTag != null) { + if (mTag.matches(".*[:|].*") == true) { //$NON-NLS-1$ + mTagWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage( + IMAGE_WARNING, + mShell.getDisplay())); + mTagWarning.setToolTipText("Tag cannot contain | or :"); //$NON-NLS-1$ + result = false; + } else { + mTagWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage( + IMAGE_EMPTY, + mShell.getDisplay())); + mTagWarning.setToolTipText(null); + } + } + + // then we check it not contains character | or : + if (mName != null && mName.length() > 0) { + if (mName.matches(".*[:|].*") == true) { //$NON-NLS-1$ + mNameWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage( + IMAGE_WARNING, + mShell.getDisplay())); + mNameWarning.setToolTipText("Name cannot contain | or :"); //$NON-NLS-1$ + result = false; + } else { + mNameWarning.setImage(ImageLoader.getDdmUiLibLoader().loadImage( + IMAGE_EMPTY, + mShell.getDisplay())); + mNameWarning.setToolTipText(null); + } + } else { + result = false; } - mOkButton.setEnabled(true); + mOkButton.setEnabled(result); } } diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogFilter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogFilter.java index 2f2cfef..74a5e37 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogFilter.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogFilter.java @@ -79,7 +79,7 @@ public class LogFilter { private LogColors mColors; private boolean mTempFilteringStatus = false; - + private final ArrayList<LogMessage> mMessages = new ArrayList<LogMessage>(); private final ArrayList<LogMessage> mNewMessages = new ArrayList<LogMessage>(); @@ -283,7 +283,7 @@ public class LogFilter { } mIsCurrentTabItem = selected; } - + /** * Adds a new message and optionally removes an old message. * <p/>The new message is filtered through {@link #accept(LogMessage)}. @@ -301,7 +301,7 @@ public class LogFilter { mMessages.remove(index); mRemovedMessageCount++; } - + // now we look for it in mNewMessages. This can happen if the new message is added // and then removed because too many messages are added between calls to #flush() index = mNewMessages.indexOf(oldMessage); @@ -322,7 +322,7 @@ public class LogFilter { return filter; } } - + /** * Removes all the items in the filter and its {@link Table}. */ @@ -332,7 +332,7 @@ public class LogFilter { mMessages.clear(); mTable.removeAll(); } - + /** * Filters a message. * @param logMessage the Message @@ -401,13 +401,13 @@ public class LogFilter { // if scroll bar is at the bottom, we will scroll ScrollBar bar = mTable.getVerticalBar(); boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb(); - + // if we are not going to scroll, get the current first item being shown. int topIndex = mTable.getTopIndex(); // disable drawing mTable.setRedraw(false); - + int totalCount = mNewMessages.size(); try { @@ -415,11 +415,12 @@ public class LogFilter { for (int i = 0 ; i < mRemovedMessageCount && mTable.getItemCount() > 0 ; i++) { mTable.remove(0); } - + mRemovedMessageCount = 0; + if (mUnreadCount > mTable.getItemCount()) { mUnreadCount = mTable.getItemCount(); } - + // add the new items for (int i = 0 ; i < totalCount ; i++) { LogMessage msg = mNewMessages.get(i); @@ -430,7 +431,7 @@ public class LogFilter { // but at least ddms won't crash. Log.e("LogFilter", e); } - + // redraw mTable.setRedraw(true); @@ -467,7 +468,7 @@ public class LogFilter { mTabItem.setText(mName); //$NON-NLS-1$ } } - + mNewMessages.clear(); } diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogPanel.java index 80e24d3..80ed6e9 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogPanel.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogPanel.java @@ -1211,13 +1211,13 @@ public class LogPanel extends SelectionDependentPanel { } else { messageIndex = mBufferEnd; + // increment the next usable slot index + mBufferEnd = (mBufferEnd + 1) % STRING_BUFFER_LENGTH; + // check we aren't overwriting start if (mBufferEnd == mBufferStart) { mBufferStart = (mBufferStart + 1) % STRING_BUFFER_LENGTH; } - - // increment the next usable slot index - mBufferEnd = (mBufferEnd + 1) % STRING_BUFFER_LENGTH; } LogMessage oldMessage = null; diff --git a/ddms/libs/ddmuilib/src/resources/images/add.png b/ddms/libs/ddmuilib/src/images/add.png Binary files differindex eefc2ca..eefc2ca 100644 --- a/ddms/libs/ddmuilib/src/resources/images/add.png +++ b/ddms/libs/ddmuilib/src/images/add.png diff --git a/ddms/libs/ddmuilib/src/resources/images/android.png b/ddms/libs/ddmuilib/src/images/android.png Binary files differindex 3779d4d..3779d4d 100644 --- a/ddms/libs/ddmuilib/src/resources/images/android.png +++ b/ddms/libs/ddmuilib/src/images/android.png diff --git a/ddms/libs/ddmuilib/src/resources/images/backward.png b/ddms/libs/ddmuilib/src/images/backward.png Binary files differindex 90a9713..90a9713 100644 --- a/ddms/libs/ddmuilib/src/resources/images/backward.png +++ b/ddms/libs/ddmuilib/src/images/backward.png diff --git a/ddms/libs/ddmuilib/src/resources/images/capture.png b/ddms/libs/ddmuilib/src/images/capture.png Binary files differindex da5c10b..da5c10b 100644 --- a/ddms/libs/ddmuilib/src/resources/images/capture.png +++ b/ddms/libs/ddmuilib/src/images/capture.png diff --git a/ddms/libs/ddmuilib/src/resources/images/clear.png b/ddms/libs/ddmuilib/src/images/clear.png Binary files differindex 0009cf6..0009cf6 100644 --- a/ddms/libs/ddmuilib/src/resources/images/clear.png +++ b/ddms/libs/ddmuilib/src/images/clear.png diff --git a/ddms/libs/ddmuilib/src/resources/images/d.png b/ddms/libs/ddmuilib/src/images/d.png Binary files differindex d45506e..d45506e 100644 --- a/ddms/libs/ddmuilib/src/resources/images/d.png +++ b/ddms/libs/ddmuilib/src/images/d.png diff --git a/ddms/libs/ddmuilib/src/resources/images/debug-attach.png b/ddms/libs/ddmuilib/src/images/debug-attach.png Binary files differindex 9b8a11c..9b8a11c 100644 --- a/ddms/libs/ddmuilib/src/resources/images/debug-attach.png +++ b/ddms/libs/ddmuilib/src/images/debug-attach.png diff --git a/ddms/libs/ddmuilib/src/resources/images/debug-error.png b/ddms/libs/ddmuilib/src/images/debug-error.png Binary files differindex f22da1f..f22da1f 100644 --- a/ddms/libs/ddmuilib/src/resources/images/debug-error.png +++ b/ddms/libs/ddmuilib/src/images/debug-error.png diff --git a/ddms/libs/ddmuilib/src/resources/images/debug-wait.png b/ddms/libs/ddmuilib/src/images/debug-wait.png Binary files differindex 322be63..322be63 100644 --- a/ddms/libs/ddmuilib/src/resources/images/debug-wait.png +++ b/ddms/libs/ddmuilib/src/images/debug-wait.png diff --git a/ddms/libs/ddmuilib/src/resources/images/delete.png b/ddms/libs/ddmuilib/src/images/delete.png Binary files differindex db5fab8..db5fab8 100644 --- a/ddms/libs/ddmuilib/src/resources/images/delete.png +++ b/ddms/libs/ddmuilib/src/images/delete.png diff --git a/ddms/libs/ddmuilib/src/resources/images/device.png b/ddms/libs/ddmuilib/src/images/device.png Binary files differindex 7dbbbb6..7dbbbb6 100644 --- a/ddms/libs/ddmuilib/src/resources/images/device.png +++ b/ddms/libs/ddmuilib/src/images/device.png diff --git a/ddms/libs/ddmuilib/src/resources/images/down.png b/ddms/libs/ddmuilib/src/images/down.png Binary files differindex f9426cb..f9426cb 100644 --- a/ddms/libs/ddmuilib/src/resources/images/down.png +++ b/ddms/libs/ddmuilib/src/images/down.png diff --git a/ddms/libs/ddmuilib/src/resources/images/e.png b/ddms/libs/ddmuilib/src/images/e.png Binary files differindex dee7c97..dee7c97 100644 --- a/ddms/libs/ddmuilib/src/resources/images/e.png +++ b/ddms/libs/ddmuilib/src/images/e.png diff --git a/ddms/libs/ddmuilib/src/resources/images/edit.png b/ddms/libs/ddmuilib/src/images/edit.png Binary files differindex b8f65bc..b8f65bc 100644 --- a/ddms/libs/ddmuilib/src/resources/images/edit.png +++ b/ddms/libs/ddmuilib/src/images/edit.png diff --git a/ddms/libs/ddmuilib/src/resources/images/empty.png b/ddms/libs/ddmuilib/src/images/empty.png Binary files differindex f021542..f021542 100644 --- a/ddms/libs/ddmuilib/src/resources/images/empty.png +++ b/ddms/libs/ddmuilib/src/images/empty.png diff --git a/ddms/libs/ddmuilib/src/resources/images/emulator.png b/ddms/libs/ddmuilib/src/images/emulator.png Binary files differindex a718042..a718042 100644 --- a/ddms/libs/ddmuilib/src/resources/images/emulator.png +++ b/ddms/libs/ddmuilib/src/images/emulator.png diff --git a/ddms/libs/ddmuilib/src/resources/images/file.png b/ddms/libs/ddmuilib/src/images/file.png Binary files differindex 043a814..043a814 100644 --- a/ddms/libs/ddmuilib/src/resources/images/file.png +++ b/ddms/libs/ddmuilib/src/images/file.png diff --git a/ddms/libs/ddmuilib/src/resources/images/folder.png b/ddms/libs/ddmuilib/src/images/folder.png Binary files differindex 7e29b1a..7e29b1a 100644 --- a/ddms/libs/ddmuilib/src/resources/images/folder.png +++ b/ddms/libs/ddmuilib/src/images/folder.png diff --git a/ddms/libs/ddmuilib/src/resources/images/forward.png b/ddms/libs/ddmuilib/src/images/forward.png Binary files differindex a97a605..a97a605 100644 --- a/ddms/libs/ddmuilib/src/resources/images/forward.png +++ b/ddms/libs/ddmuilib/src/images/forward.png diff --git a/ddms/libs/ddmuilib/src/resources/images/gc.png b/ddms/libs/ddmuilib/src/images/gc.png Binary files differindex 5194806..5194806 100644 --- a/ddms/libs/ddmuilib/src/resources/images/gc.png +++ b/ddms/libs/ddmuilib/src/images/gc.png diff --git a/ddms/libs/ddmuilib/src/resources/images/halt.png b/ddms/libs/ddmuilib/src/images/halt.png Binary files differindex 10e3720..10e3720 100644 --- a/ddms/libs/ddmuilib/src/resources/images/halt.png +++ b/ddms/libs/ddmuilib/src/images/halt.png diff --git a/ddms/libs/ddmuilib/src/resources/images/heap.png b/ddms/libs/ddmuilib/src/images/heap.png Binary files differindex e3aa3f0..e3aa3f0 100644 --- a/ddms/libs/ddmuilib/src/resources/images/heap.png +++ b/ddms/libs/ddmuilib/src/images/heap.png diff --git a/ddms/libs/ddmuilib/src/resources/images/hprof.png b/ddms/libs/ddmuilib/src/images/hprof.png Binary files differindex 123d062..123d062 100644 --- a/ddms/libs/ddmuilib/src/resources/images/hprof.png +++ b/ddms/libs/ddmuilib/src/images/hprof.png diff --git a/ddms/libs/ddmuilib/src/resources/images/i.png b/ddms/libs/ddmuilib/src/images/i.png Binary files differindex 98385c5..98385c5 100644 --- a/ddms/libs/ddmuilib/src/resources/images/i.png +++ b/ddms/libs/ddmuilib/src/images/i.png diff --git a/ddms/libs/ddmuilib/src/resources/images/importBug.png b/ddms/libs/ddmuilib/src/images/importBug.png Binary files differindex f5da179..f5da179 100644 --- a/ddms/libs/ddmuilib/src/resources/images/importBug.png +++ b/ddms/libs/ddmuilib/src/images/importBug.png diff --git a/ddms/libs/ddmuilib/src/resources/images/load.png b/ddms/libs/ddmuilib/src/images/load.png Binary files differindex 9e7bf6e..9e7bf6e 100644 --- a/ddms/libs/ddmuilib/src/resources/images/load.png +++ b/ddms/libs/ddmuilib/src/images/load.png diff --git a/ddms/libs/ddmuilib/src/resources/images/pause.png b/ddms/libs/ddmuilib/src/images/pause.png Binary files differindex 19d286d..19d286d 100644 --- a/ddms/libs/ddmuilib/src/resources/images/pause.png +++ b/ddms/libs/ddmuilib/src/images/pause.png diff --git a/ddms/libs/ddmuilib/src/resources/images/play.png b/ddms/libs/ddmuilib/src/images/play.png Binary files differindex d54f013..d54f013 100644 --- a/ddms/libs/ddmuilib/src/resources/images/play.png +++ b/ddms/libs/ddmuilib/src/images/play.png diff --git a/ddms/libs/ddmuilib/src/resources/images/pull.png b/ddms/libs/ddmuilib/src/images/pull.png Binary files differindex f48f1b1..f48f1b1 100644 --- a/ddms/libs/ddmuilib/src/resources/images/pull.png +++ b/ddms/libs/ddmuilib/src/images/pull.png diff --git a/ddms/libs/ddmuilib/src/resources/images/push.png b/ddms/libs/ddmuilib/src/images/push.png Binary files differindex 6222864..6222864 100644 --- a/ddms/libs/ddmuilib/src/resources/images/push.png +++ b/ddms/libs/ddmuilib/src/images/push.png diff --git a/ddms/libs/ddmuilib/src/resources/images/save.png b/ddms/libs/ddmuilib/src/images/save.png Binary files differindex 040ebda..040ebda 100644 --- a/ddms/libs/ddmuilib/src/resources/images/save.png +++ b/ddms/libs/ddmuilib/src/images/save.png diff --git a/ddms/libs/ddmuilib/src/resources/images/sort_down.png b/ddms/libs/ddmuilib/src/images/sort_down.png Binary files differindex 2d4ccc1..2d4ccc1 100644 --- a/ddms/libs/ddmuilib/src/resources/images/sort_down.png +++ b/ddms/libs/ddmuilib/src/images/sort_down.png diff --git a/ddms/libs/ddmuilib/src/resources/images/sort_up.png b/ddms/libs/ddmuilib/src/images/sort_up.png Binary files differindex 3a0bc3c..3a0bc3c 100644 --- a/ddms/libs/ddmuilib/src/resources/images/sort_up.png +++ b/ddms/libs/ddmuilib/src/images/sort_up.png diff --git a/ddms/libs/ddmuilib/src/resources/images/thread.png b/ddms/libs/ddmuilib/src/images/thread.png Binary files differindex ac839e8..ac839e8 100644 --- a/ddms/libs/ddmuilib/src/resources/images/thread.png +++ b/ddms/libs/ddmuilib/src/images/thread.png diff --git a/ddms/libs/ddmuilib/src/resources/images/tracing_start.png b/ddms/libs/ddmuilib/src/images/tracing_start.png Binary files differindex 88771cc..88771cc 100644 --- a/ddms/libs/ddmuilib/src/resources/images/tracing_start.png +++ b/ddms/libs/ddmuilib/src/images/tracing_start.png diff --git a/ddms/libs/ddmuilib/src/resources/images/tracing_stop.png b/ddms/libs/ddmuilib/src/images/tracing_stop.png Binary files differindex 71bd215..71bd215 100644 --- a/ddms/libs/ddmuilib/src/resources/images/tracing_stop.png +++ b/ddms/libs/ddmuilib/src/images/tracing_stop.png diff --git a/ddms/libs/ddmuilib/src/resources/images/up.png b/ddms/libs/ddmuilib/src/images/up.png Binary files differindex 92edf5a..92edf5a 100644 --- a/ddms/libs/ddmuilib/src/resources/images/up.png +++ b/ddms/libs/ddmuilib/src/images/up.png diff --git a/ddms/libs/ddmuilib/src/resources/images/v.png b/ddms/libs/ddmuilib/src/images/v.png Binary files differindex 8044051..8044051 100644 --- a/ddms/libs/ddmuilib/src/resources/images/v.png +++ b/ddms/libs/ddmuilib/src/images/v.png diff --git a/ddms/libs/ddmuilib/src/resources/images/w.png b/ddms/libs/ddmuilib/src/images/w.png Binary files differindex 129d0f9..129d0f9 100644 --- a/ddms/libs/ddmuilib/src/resources/images/w.png +++ b/ddms/libs/ddmuilib/src/images/w.png diff --git a/ddms/libs/ddmuilib/src/resources/images/warning.png b/ddms/libs/ddmuilib/src/images/warning.png Binary files differindex ca3b6ed..ca3b6ed 100644 --- a/ddms/libs/ddmuilib/src/resources/images/warning.png +++ b/ddms/libs/ddmuilib/src/images/warning.png diff --git a/draw9patch/Android.mk b/draw9patch/Android.mk index 934495d..f8de8d6 100644 --- a/draw9patch/Android.mk +++ b/draw9patch/Android.mk @@ -12,6 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -DRAW9PATCH_LOCAL_DIR := $(call my-dir) -include $(DRAW9PATCH_LOCAL_DIR)/etc/Android.mk -include $(DRAW9PATCH_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +LOCAL_JAVA_LIBRARIES := \ + swing-worker-1.1 + +LOCAL_MODULE := draw9patch + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/draw9patch/NOTICE b/draw9patch/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/draw9patch/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/draw9patch/src/com/android/draw9patch/ui/MainFrame.java b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java index d5b6409..57f6cd9 100644 --- a/draw9patch/src/com/android/draw9patch/ui/MainFrame.java +++ b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java @@ -40,9 +40,15 @@ public class MainFrame extends JFrame { private JMenuItem saveMenuItem; private ImageEditorPanel imageEditor; + private static final String TITLE_FORMAT = "Draw 9-patch: %s"; + public MainFrame(String path) throws HeadlessException { super("Draw 9-patch"); + if (path != null) { + setTitle(String.format(TITLE_FORMAT, path)); + } + buildActions(); buildMenuBar(); buildContent(); @@ -164,6 +170,7 @@ public class MainFrame extends JFrame { protected void done() { try { showImageEditor(get(), file.getAbsolutePath()); + setTitle(String.format(TITLE_FORMAT, file.getAbsolutePath())); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { diff --git a/draw9patch/src/resources/images/checker.png b/draw9patch/src/images/checker.png Binary files differindex 78908f4..78908f4 100644 --- a/draw9patch/src/resources/images/checker.png +++ b/draw9patch/src/images/checker.png diff --git a/draw9patch/src/resources/images/drop.png b/draw9patch/src/images/drop.png Binary files differindex 7a7436a..7a7436a 100644 --- a/draw9patch/src/resources/images/drop.png +++ b/draw9patch/src/images/drop.png diff --git a/dumpeventlog/NOTICE b/dumpeventlog/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/dumpeventlog/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/eclipse/changes.txt b/eclipse/changes.txt index 5c76898..17c43fd 100644 --- a/eclipse/changes.txt +++ b/eclipse/changes.txt @@ -1,3 +1,105 @@ +11.0.0 +- Visual Refactoring: + - The new "Extract Style" refactoring pulls out style constants and + defines them as style rules intead. + - The new "Wrap in Container" refactoring surrounds the selected + views with a new layout, and transfers namespace and layout + parameters to the new parent + - The new "Change Widget Type" refactoring changes the type of the + selected views to a new type. (Also, a new selection context menu + in the visual layout editor makes it easy to select siblings as + well as views anywhere in the layout that have the same type). + - The new "Change Layout" refactoring changes layouts from one type + to another, and can also flatten a layout hierarchy. + - The "Extract as Include" refactoring now finds identical fragments + in other layouts and offers to combine all into a single include. + - There is a new Refactoring Quick Assistant which can be invoked + from the XML editor (with Ctrl-1) to apply any of the above + refactorings (and Extract String) to the current selection. +- Visual Layout Editor: + - Improved "rendering fidelity": The layout preview has been + improved and should more closely match the rendering on actual + devices. + - The visual editor now previews ListViews at designtime. By + default, a two-line list item is shown, but with a context menu + you can pick any arbitrary layout to be used for the list items, + and you can also pick the header and footer layouts. + - The palette now supports "configurations" where a single view is + presented in various different configurations. For example, there + is a whole "Textfields" palette category where the EditText view + can be dragged in as a password field, an e-mail field, a phone + field, and so on. Similarly, TextViews are offered preconfigured + with large, normal and small theme sizes, and LinearLayouts are + offered both in horizontal and vertical configurations. + - The palette supports custom views, picking up any custom + implementations of the View class in your project source folders + or in included libraries, and these can be dragged into layouts. + - The layout editor automatically applies a "zoom to fit" for newly + opened files as well as on device size and orientation changes to + ensure that large layouts are always fully visible unless you + manually zoom in. + - You can drop an "include" tag from the palette, which will pop up + a layout chooser, and the chosen layout is added as an include. + Similarly, dropping images or image buttons will pop up image + resource choosers to initialize the new image with. + - The configuration chooser now applies the "Render Target" and + "Locale" settings project wide, making it trivial to check the + layouts for different languages or render targets without having + to configure these individually for each layout. + - The layout editor is smarter about picking a default theme to + render a layout with, consulting factors like theme registrations + in the manifest, the SDK version, etc. +- XML editors: + - Code completion has been significantly improved. It now works + within <style> elements, it completes dimensional units, + it sorts resource paths in values based on the attribute name, + etc. There are also many fixes to handle text replacement. + - AAPT errors are handled better. They are now underlined for the + relevant range in the editor, and a new quickfix makes it trivial + to create missing resources. + - Code completion for drawable, animation and color XML files. +- DDMS: + - "New Folder" action in the File Explorer + - The screenshot dialog will add timestamps to the filenames, and + preserve the orientation on snapshot refresh +- TraceView: Mouse-wheel zoom support in the timeline +- The New Android Project wizard now supports Eclipse working sets +- Most of the tools have improved integration with the Mac OSX + system menu bar. +- Most of the tools have new launcher icons. + +10.0.1 +- Temporary work-around to resolve the rare cases in which the layout + editor will not open. +- Fix issue in which ADT 10.0.0 would install on Eclipse 3.4 and + lower, even though ADT requires Eclipse 3.5 or higher (as of + 10.0.0). + +10.0.0 +- The tools now automatically generate Java Programming Language + source files (in the gen/ directory) and bytecode (in the res/raw/ + directory) from your .rs files. +- A Binary XML editor has been added. +- Traceview is now integrated into the Eclipse UI. +- The "Go To Declaration" feature for XML and .java files quickly show + all the matches in the project and allows you jump to specific items + such as string translations or onClick handlers. +- The Resource Chooser can create items such as dimensions, integers, + ids, and booleans. +- Improvements to the Visual Layout Editor: + - A new Palette with categories and rendering previews. + - A Layout Actions bar that provides quick access to common layout + operations. + - When the Android 3.0 rendering library is selected, layouts render + more like they do on devices. This includes rendering of status + and title bars to more accurately reflect the actual screen space + available to applications. + - Zoom improvements such as fit to view, persistent scale, and + keyboard access.. + - Further improvements to <merge> layouts, as well as layouts with + gesture overlays. + - Improved rendering error diagnostics. + 9.0.0 - Visual Layout Editor - Empty layouts with 0,0 size are now automatically expanded when diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index 235e9f2..9291f47 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -16,6 +16,8 @@ apk app apps arg +async +attr attrs avd avds @@ -63,8 +65,10 @@ dex dexified diff diffs +dir dirs ditto +docs dpi drawable drawables @@ -80,9 +84,11 @@ foreach fqcn framelayout gen +git groovy guava hardcoded +hardcodes hotfix href http @@ -99,6 +105,8 @@ inline instanceof instantiatable int +interpolator +interpolators iterable javac javadoc @@ -118,15 +126,20 @@ lowercase luminance mac macs +malformed marquee metadata min +mipmap monte +ms +msg multi multimap multimaps namespace namespaces +newfound num ok os @@ -136,11 +149,13 @@ pings placeholder plugin popup +popups pre precompiler pref prefs preload +preloaded preloads primordial printf @@ -149,12 +164,14 @@ programmatically proguard proxies proxy +quickfix recompilation rect redo refactor refactoring regexp +regexps registry reindent remap @@ -185,6 +202,8 @@ stderr stdout stretchiness struct +styleable +styleables subclassing submenu supertype @@ -201,9 +220,11 @@ tooltip tooltips traceview translucency +typo ui uncomment undescribed +undoable unhide unicode uninstall diff --git a/eclipse/features/com.android.ide.eclipse.adt/feature.xml b/eclipse/features/com.android.ide.eclipse.adt/feature.xml index 2676c32..aeea750 100644 --- a/eclipse/features/com.android.ide.eclipse.adt/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.adt/feature.xml @@ -2,7 +2,7 @@ <feature id="com.android.ide.eclipse.adt" label="Android Development Tools" - version="10.0.0.qualifier" + version="11.0.0.qualifier" provider-name="The Android Open Source Project" plugin="com.android.ide.eclipse.adt"> @@ -113,7 +113,7 @@ This Agreement is governed by the laws of the State of New York and the intellec <requires> <import plugin="com.android.ide.eclipse.ddms" match="perfect"/> - <import plugin="org.eclipse.core.runtime"/> + <import plugin="org.eclipse.core.runtime" version="3.5" match="greaterOrEqual"/> <import plugin="org.eclipse.core.resources"/> <import plugin="org.eclipse.debug.core"/> <import plugin="org.eclipse.debug.ui"/> @@ -127,7 +127,7 @@ This Agreement is governed by the laws of the State of New York and the intellec <import plugin="org.eclipse.ui.workbench.texteditor"/> <import plugin="org.eclipse.ui.console"/> <import plugin="org.eclipse.core.filesystem"/> - <import plugin="org.eclipse.ui"/> + <import plugin="org.eclipse.ui" version="3.5" match="greaterOrEqual"/> <import plugin="org.eclipse.ui.ide"/> <import plugin="org.eclipse.ui.forms"/> <import plugin="org.eclipse.gef"/> diff --git a/eclipse/features/com.android.ide.eclipse.ddms/feature.xml b/eclipse/features/com.android.ide.eclipse.ddms/feature.xml index 31ef379..cc13984 100644 --- a/eclipse/features/com.android.ide.eclipse.ddms/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.ddms/feature.xml @@ -2,7 +2,7 @@ <feature id="com.android.ide.eclipse.ddms" label="Android DDMS" - version="10.0.0.qualifier" + version="11.0.0.qualifier" provider-name="The Android Open Source Project" plugin="com.android.ide.eclipse.ddms"> @@ -228,8 +228,8 @@ </url> <requires> - <import plugin="org.eclipse.ui"/> - <import plugin="org.eclipse.core.runtime"/> + <import plugin="org.eclipse.ui" version="3.5" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.runtime" version="3.5" match="greaterOrEqual"/> <import plugin="org.eclipse.ui.console"/> <import plugin="org.eclipse.core.resources"/> <import plugin="org.eclipse.ui.ide"/> diff --git a/eclipse/features/com.android.ide.eclipse.hierarchyviewer/feature.xml b/eclipse/features/com.android.ide.eclipse.hierarchyviewer/feature.xml index e2e869b..9323cb0 100644 --- a/eclipse/features/com.android.ide.eclipse.hierarchyviewer/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.hierarchyviewer/feature.xml @@ -2,7 +2,7 @@ <feature id="com.android.ide.eclipse.hierarchyviewer" label="Android Hierarchy Viewer" - version="10.0.0.qualifier" + version="11.0.0.qualifier" provider-name="The Android Open Source Project" plugin="com.android.ide.eclipse.hierarchyviewer"> @@ -223,8 +223,8 @@ </url> <requires> - <import plugin="org.eclipse.ui"/> - <import plugin="org.eclipse.core.runtime"/> + <import plugin="org.eclipse.ui" version="3.5" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.runtime" version="3.5" match="greaterOrEqual"/> <import plugin="org.eclipse.ui.console"/> <import plugin="com.android.ide.eclipse.ddms" match="perfect"/> </requires> diff --git a/eclipse/features/com.android.ide.eclipse.pdt/feature.xml b/eclipse/features/com.android.ide.eclipse.pdt/feature.xml index f1e767b..06eadb2 100644 --- a/eclipse/features/com.android.ide.eclipse.pdt/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.pdt/feature.xml @@ -2,7 +2,7 @@ <feature id="com.android.ide.eclipse.pdt" label="Android Platform Development Tools" - version="10.0.0.qualifier" + version="11.0.0.qualifier" provider-name="The Android Open Source Project"> <description> diff --git a/eclipse/features/com.android.ide.eclipse.tests/feature.xml b/eclipse/features/com.android.ide.eclipse.tests/feature.xml index d8d5e63..1fb4ee7 100644 --- a/eclipse/features/com.android.ide.eclipse.tests/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.tests/feature.xml @@ -2,7 +2,7 @@ <feature id="com.android.ide.eclipse.tests" label="ADT Tests" - version="10.0.0.qualifier" + version="11.0.0.qualifier" provider-name="The Android Open Source Project"> <copyright> diff --git a/eclipse/features/com.android.ide.eclipse.traceview/feature.xml b/eclipse/features/com.android.ide.eclipse.traceview/feature.xml index bebc92a..4eceaa5 100644 --- a/eclipse/features/com.android.ide.eclipse.traceview/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.traceview/feature.xml @@ -2,7 +2,7 @@ <feature id="com.android.ide.eclipse.traceview" label="Android Traceview" - version="10.0.0.qualifier" + version="11.0.0.qualifier" provider-name="The Android Open Source Project" plugin="com.android.ide.eclipse.traceview"> @@ -223,8 +223,8 @@ </url> <requires> - <import plugin="org.eclipse.ui"/> - <import plugin="org.eclipse.core.runtime"/> + <import plugin="org.eclipse.ui" version="3.5" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.runtime" version="3.5" match="greaterOrEqual"/> <import plugin="org.eclipse.ui.ide"/> <import plugin="com.android.ide.eclipse.ddms" version="10.0.0" match="greaterOrEqual"/> <import plugin="org.eclipse.core.filesystem"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.eclipse.jdt.core.prefs b/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..f4696e4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 16 15:10:40 PDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs b/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs index ecb8e78..a1c97ef 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs +++ b/eclipse/plugins/com.android.ide.eclipse.adt/.settings/org.moreunit.prefs @@ -1,4 +1,4 @@ -#Wed Nov 24 07:39:01 PST 2010 +#Mon Feb 28 13:14:31 PST 2011 eclipse.preferences.version=1 -org.moreunit.unitsourcefolder=adt\:src\:adt-tests\:unittests +org.moreunit.unitsourcefolder=adt\:src\:adt-tests\:unittests\#adt\:src\:adt-tests\:src org.moreunit.useprojectsettings=true diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF index bea92fe..5214736 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Android Development Toolkit Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true -Bundle-Version: 10.0.0.qualifier +Bundle-Version: 11.0.0.qualifier Bundle-ClassPath: ., libs/androidprefs.jar, libs/sdklib.jar, @@ -47,7 +47,8 @@ Require-Bundle: com.android.ide.eclipse.ddms, org.eclipse.ltk.ui.refactoring, org.eclipse.core.expressions Eclipse-LazyStart: true -Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.tests", +Export-Package: com.android, + com.android.annotations;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.api;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.layout;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.log;x-friends:="com.android.ide.eclipse.tests", @@ -55,6 +56,7 @@ Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.test com.android.ide.common.rendering.api;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.rendering.legacy;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.resources;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.common.resources.configuration;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.resources.platform;x-friends:="com.android.ide.eclipse.tests", com.android.ide.common.sdk;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt;x-friends:="com.android.ide.eclipse.tests", @@ -63,6 +65,7 @@ Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.test com.android.ide.eclipse.adt.internal.build;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.build.builders;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.adt.internal.editors.binaryxml, com.android.ide.eclipse.adt.internal.editors.descriptors;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.export;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.layout;x-friends:="com.android.ide.eclipse.tests", @@ -70,6 +73,7 @@ Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.test com.android.ide.eclipse.adt.internal.editors.layout.descriptors;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.layout.gle2;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.layout.gre;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.adt.internal.editors.layout.refactoring, com.android.ide.eclipse.adt.internal.editors.layout.uimodel;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.manifest;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.editors.manifest.descriptors;x-friends:="com.android.ide.eclipse.tests", @@ -96,7 +100,6 @@ Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.test com.android.ide.eclipse.adt.internal.refactorings.extractstring;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.refactorings.renamepackage;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.resources;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.adt.internal.resources.configurations;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.resources.manager;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.sdk;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.sourcelookup;x-friends:="com.android.ide.eclipse.tests", @@ -106,6 +109,7 @@ Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.test com.android.ide.eclipse.adt.internal.wizards.newproject;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.internal.wizards.newxmlfile;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.io;x-friends:="com.android.ide.eclipse.tests", + com.android.io;x-friends:="com.android.ide.eclipse.tests", com.android.layoutlib.api;x-friends:="com.android.ide.eclipse.tests", com.android.ninepatch;x-friends:="com.android.ide.eclipse.tests", com.android.prefs;x-friends:="com.android.ide.eclipse.tests", @@ -117,7 +121,6 @@ Export-Package: com.android.annotations;x-friends:="com.android.ide.eclipse.test com.android.sdklib.internal.export;x-friends:="com.android.ide.eclipse.tests", com.android.sdklib.internal.project;x-friends:="com.android.ide.eclipse.tests", com.android.sdklib.internal.repository;x-friends:="com.android.ide.eclipse.tests", - com.android.sdklib.io;x-friends:="com.android.ide.eclipse.tests", com.android.sdklib.repository;x-friends:="com.android.ide.eclipse.tests", com.android.sdklib.util;x-friends:="com.android.ide.eclipse.tests", com.android.sdklib.xml;x-friends:="com.android.ide.eclipse.tests", diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/about.ini b/eclipse/plugins/com.android.ide.eclipse.adt/about.ini index 560e475..2d7cdaa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/about.ini +++ b/eclipse/plugins/com.android.ide.eclipse.adt/about.ini @@ -1,2 +1,2 @@ aboutText=%blurb -featureImage=icons/android_32x32.png
\ No newline at end of file +featureImage=icons/android-32.png
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/SearchView.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/SearchView.png Binary files differindex c41a2cd..2b45fc2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/SearchView.png +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/SearchView.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/VerticalLinearLayout.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/VerticalLinearLayout.png Binary files differnew file mode 100644 index 0000000..e03c16e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/VerticalLinearLayout.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/android-32.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/android-32.png Binary files differnew file mode 100644 index 0000000..4e0cc13 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/android-32.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/android-64.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/android-64.png Binary files differnew file mode 100644 index 0000000..2c6f149 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/android-64.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/android_32x32.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/android_32x32.png Binary files differdeleted file mode 100644 index a382aad..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/android_32x32.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/android_large.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/android_large.png Binary files differdeleted file mode 100644 index e0ca992..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/icons/android_large.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/attribute.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/attribute.png Binary files differnew file mode 100644 index 0000000..04508ea --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/attribute.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/draw9patch-16.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/draw9patch-16.png Binary files differnew file mode 100644 index 0000000..cc080e7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/draw9patch-16.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/element.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/element.png Binary files differnew file mode 100644 index 0000000..e5cd0ca --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/element.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/refresh.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/refresh.png Binary files differnew file mode 100644 index 0000000..7cbebf4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/refresh.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/requestFocus.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/requestFocus.png Binary files differnew file mode 100644 index 0000000..205a032 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/requestFocus.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/view_menu.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/view_menu.png Binary files differnew file mode 100644 index 0000000..8575107 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/view_menu.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 551e6be..a16a766 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -49,6 +49,11 @@ <super type="org.eclipse.core.resources.textmarker"/> <persistent value="true"/> </extension> + <extension point="org.eclipse.ui.ide.markerResolution"> + <markerResolutionGenerator + markerType="com.android.ide.eclipse.common.aaptProblem" + class="com.android.ide.eclipse.adt.internal.build.AaptQuickFix"/> + </extension> <extension id="ResourceManagerBuilder" name="Android Resource Manager" @@ -146,7 +151,7 @@ modes="debug, run" name="Android Application" public="true" - sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector" + sourceLocatorId="com.android.ide.eclipse.adt.internal.sourcelookup.AdtSourceLookupDirector" sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer"> </launchConfigurationType> </extension> @@ -527,6 +532,41 @@ id="com.android.ide.eclipse.editors.xml.XmlEditor" name="Android Xml Resources Editor"> </editor> + <editor + class="com.android.ide.eclipse.adt.internal.editors.animator.AnimationEditor" + default="false" + extensions="xml" + icon="icons/android_file.png" + id="com.android.ide.eclipse.editors.animator.AnimationEditor" + name="Android Animation Editor"> + </editor> + <editor + class="com.android.ide.eclipse.adt.internal.editors.drawable.DrawableEditor" + default="false" + extensions="xml" + icon="icons/android_file.png" + id="com.android.ide.eclipse.editors.drawable.DrawableEditor" + name="Android Drawable Editor"> + </editor> + <editor + class="com.android.ide.eclipse.adt.internal.editors.color.ColorEditor" + default="false" + extensions="xml" + icon="icons/android_file.png" + id="com.android.ide.eclipse.editors.color.ColorEditor" + name="Android Color Editor"> + </editor> + <editor + class="com.android.ide.eclipse.adt.internal.editors.binaryxml.BinaryXMLMultiPageEditorPart" + contributorClass="org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorActionBarContributor" + icon="$nl$/icons/android_file.png" + id="com.android.ide.eclipse.adt.binedit.BinaryXMLMultiPageEditorPart" + name="Android Binary XML editor" + symbolicFontName="org.eclipse.wst.sse.ui.textfont"> + <contentTypeBinding + contentTypeId="com.android.ide.eclipse.adt.binaryXml"> + </contentTypeBinding> + </editor> </extension> <extension point="org.eclipse.ui.views"> @@ -534,7 +574,7 @@ allowMultiple="false" category="com.android.ide.eclipse.ddms.views.category" class="com.android.ide.eclipse.adt.internal.ui.ResourceExplorerView" - icon="icons/android.png" + icon="icons/draw9patch-16.png" id="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView" name="Resource Explorer"> </view> @@ -561,6 +601,26 @@ class="com.android.ide.eclipse.adt.internal.editors.xml.XmlSourceViewerConfig" target="com.android.ide.eclipse.editors.xml.XmlEditor"> </sourceViewerConfiguration> + <sourceViewerConfiguration + class="com.android.ide.eclipse.adt.internal.editors.animator.AnimationSourceViewerConfig" + target="com.android.ide.eclipse.editors.animator.AnimationEditor"> + </sourceViewerConfiguration> + <sourceViewerConfiguration + class="com.android.ide.eclipse.adt.internal.editors.drawable.DrawableSourceViewerConfig" + target="com.android.ide.eclipse.editors.drawable.DrawableEditor"> + </sourceViewerConfiguration> + <sourceViewerConfiguration + class="com.android.ide.eclipse.adt.internal.editors.color.ColorSourceViewerConfig" + target="com.android.ide.eclipse.editors.color.ColorEditor"> + </sourceViewerConfiguration> + <provisionalConfiguration + type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor" + class="com.android.ide.eclipse.adt.internal.build.AaptQuickFix" + target="org.eclipse.wst.xml.XML_DEFAULT" /> + <provisionalConfiguration + type="org.eclipse.jface.text.quickassist.IQuickAssistProcessor" + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.RefactoringAssistant" + target="org.eclipse.wst.xml.XML_DEFAULT" /> </extension> <extension point="org.eclipse.ui.propertyPages"> @@ -652,6 +712,51 @@ style="push" tooltip="Extracts a string into Android resource string"> </action> + <action + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeAction" + definitionId="com.android.ide.eclipse.adt.refactoring.extract.include" + id="com.android.ide.eclipse.adt.actions.ExtractInclude" + label="Extract as Include..." + menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android" + style="push" + tooltip="Extracts Views as Included Layout"> + </action> + <action + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction" + definitionId="com.android.ide.eclipse.adt.refactoring.extract.style" + id="com.android.ide.eclipse.adt.actions.ExtractStyle" + label="Extract Style..." + menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android" + style="push" + tooltip="Extracts Styles"> + </action> + <action + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction" + definitionId="com.android.ide.eclipse.adt.refactoring.wrapin" + id="com.android.ide.eclipse.adt.actions.WrapIn" + label="Wrap In Container..." + menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android" + style="push" + tooltip="Wraps Views in a new container"> + </action> + <action + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutAction" + definitionId="com.android.ide.eclipse.adt.refactoring.convert" + id="com.android.ide.eclipse.adt.actions.ChangeLayout" + label="Change Layout..." + menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android" + style="push" + tooltip="Changes layouts from one type to another"> + </action> + <action + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction" + definitionId="com.android.ide.eclipse.adt.refactoring.changeview" + id="com.android.ide.eclipse.adt.actions.ChangeView" + label="Change Widget Type..." + menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android" + style="push" + tooltip="Changes the type of the selected widgets"> + </action> <menu id="org.eclipse.jdt.ui.refactoring.menu" label="Refactor"> @@ -761,6 +866,36 @@ id="com.android.ide.eclipse.adt.refactoring.extract.string" name="Extract Android String"> </command> + <command + categoryId="com.android.ide.eclipse.adt.refactoring.category" + description="Extract Views as Included Layout" + id="com.android.ide.eclipse.adt.refactoring.extract.include" + name="Extract as Include"> + </command> + <command + categoryId="com.android.ide.eclipse.adt.refactoring.category" + description="Extract Styles" + id="com.android.ide.eclipse.adt.refactoring.extract.style" + name="Extract Styles"> + </command> + <command + categoryId="com.android.ide.eclipse.adt.refactoring.category" + description="Wraps Views in a New Container" + id="com.android.ide.eclipse.adt.refactoring.wrapin" + name="Wrap in Container"> + </command> + <command + categoryId="com.android.ide.eclipse.adt.refactoring.category" + description="Converts Layouts from One Type to Another" + id="com.android.ide.eclipse.adt.refactoring.convert" + name="Change Layout"> + </command> + <command + categoryId="com.android.ide.eclipse.adt.refactoring.category" + description="Changes the widget type for the selection" + id="com.android.ide.eclipse.adt.refactoring.changeview" + name="Change Widget Type"> + </command> </extension> <extension point="org.eclipse.ltk.core.refactoring.refactoringContributions"> @@ -768,6 +903,26 @@ class="com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringContribution" id="com.android.ide.eclipse.adt.refactoring.extract.string"> </contribution> + <contribution + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeContribution" + id="com.android.ide.eclipse.adt.refactoring.extract.include"> + </contribution> + <contribution + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleContribution" + id="com.android.ide.eclipse.adt.refactoring.extract.style"> + </contribution> + <contribution + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInContribution" + id="com.android.ide.eclipse.adt.refactoring.wrapin"> + </contribution> + <contribution + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutContribution" + id="com.android.ide.eclipse.adt.refactoring.convert"> + </contribution> + <contribution + class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewContribution" + id="com.android.ide.eclipse.adt.refactoring.changeview"> + </contribution> </extension> <extension point="org.eclipse.core.expressions.propertyTesters"> @@ -910,25 +1065,14 @@ commandId="com.android.ide.eclipse.adt.launch.LaunchShortcut.run" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"/> </extension> - <extension point="org.eclipse.core.contenttype.contentTypes"> - <content-type id="com.android.ide.eclipse.adt.binaryXml" name="Android Binary XML" - priority="high" - file-extensions="xml"> - <describer - class="com.android.ide.eclipse.adt.internal.editors.binaryxml.BinaryXMLDescriber"> - </describer> + <!-- NOTE: deactivated temporarily due to bug 15003. + extension point="org.eclipse.core.contenttype.contentTypes"> + <content-type + describer="com.android.ide.eclipse.adt.internal.editors.binaryxml.BinaryXMLDescriber" + file-extensions="xml" + id="com.android.ide.eclipse.adt.binaryXml" + name="Android Binary XML" + priority="high"> </content-type> - </extension> - <extension point="org.eclipse.ui.editors"> - <editor - name="Android Binary XML editor" - icon="$nl$/icons/android_file.png" - contributorClass="org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorActionBarContributor" - class="com.android.ide.eclipse.adt.internal.editors.binaryxml.BinaryXMLMultiPageEditorPart" - symbolicFontName="org.eclipse.wst.sse.ui.textfont" - id="com.android.ide.eclipse.adt.binedit.BinaryXMLMultiPageEditorPart"> - <contentTypeBinding - contentTypeId="com.android.ide.eclipse.adt.binaryXml" /> - </editor> - </extension> + </extension --> </plugin> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java index 0938afa..7317ebc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/DrawingStyle.java @@ -44,6 +44,12 @@ public enum DrawingStyle { HOVER, /** + * The style used for hovered views (e.g. when the mouse is directly on top + * of the view), when the hover happens to be the same object as the selection + */ + HOVER_SELECTION, + + /** * The style used to draw anchors (lines to the other views the given view * is anchored to) */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java index efd8086..6aa4776 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java @@ -19,6 +19,8 @@ package com.android.ide.common.api; import com.android.annotations.Nullable; +import java.util.Collection; + /** * A Client Rules Engine is a set of methods that {@link IViewRule}s can use to * access the client public API of the Rules Engine. Rules can access it via @@ -133,5 +135,22 @@ public interface IClientRulesEngine { * right, top and bottom margins respectively */ String[] displayMarginInput(String all, String left, String right, String top, String bottom); + + /** + * Displays an input dialog tailored for inputing the source of an {@code <include>} + * layout tag. This is similar to {@link #displayResourceInput} for resource type + * "layout", but should also attempt to filter out layout resources that cannot be + * included from the current context (because it would result in a cyclic dependency). + * + * @return the layout resource to include + */ + String displayIncludeSourceInput(); + + /** + * Select the given nodes + * + * @param nodes the nodes to be selected, never null + */ + void select(Collection<INode> nodes); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/INode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/INode.java index 8c2bed1..dd64dfa 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/INode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/INode.java @@ -128,6 +128,15 @@ public interface INode { INode insertChildAt(String viewFqcn, int index); /** + * Removes the given XML element child from this node's list of children. + * <p/> + * This call must be done in the context of editXml(). + * + * @param node The child to be deleted. + */ + void removeChild(INode node); + + /** * Sets an attribute for the underlying XML element. * Attributes are not written immediately -- instead the XML editor batches edits and * then commits them all together at once later. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/InsertType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/InsertType.java index 15d3a98..c5a4435 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/InsertType.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/InsertType.java @@ -24,6 +24,12 @@ public enum InsertType { /** The view is newly created (by for example a palette drag) */ CREATE, + /** + * Same as {@link #CREATE} but when the views are constructed for previewing, for + * example as part of a palette drag. + */ + CREATE_PREVIEW, + /** The view is being inserted here because it was moved from somewhere else */ MOVE, @@ -32,4 +38,15 @@ public enum InsertType { * (including drags, but not from the palette) */ PASTE; + + /** + * Returns true if this insert type is for a newly created view (for example a by + * palette drag). Note that this includes both normal create events as well as well as + * views created as part of previewing operations. + * + * @return true if this {@link InsertType} is for a newly created view + */ + public boolean isCreate() { + return this == CREATE || this == CREATE_PREVIEW; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java index 4af4559..47fcbd7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java @@ -17,6 +17,7 @@ package com.android.ide.common.layout; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.VALUE_N_DP; import com.android.ide.common.api.DrawingStyle; import com.android.ide.common.api.DropFeedback; @@ -189,9 +190,9 @@ public class AbsoluteLayoutRule extends BaseLayoutRule { } newChild.setAttribute(ANDROID_URI, "layout_x", //$NON-NLS-1$ - x + "dip"); //$NON-NLS-1$ + String.format(VALUE_N_DP, x)); newChild.setAttribute(ANDROID_URI, "layout_y", //$NON-NLS-1$ - y + "dip"); //$NON-NLS-1$ + String.format(VALUE_N_DP, y)); addInnerElements(newChild, element, idMap); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java index d21c43d..18ff320 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java @@ -189,7 +189,7 @@ public class BaseViewRule implements IViewRule { String newText = mRulesEngine.displayResourceInput("string", oldText); //$NON-NLS-1$ if (newText != null) { node.editXml("Change Text", new PropertySettingNodeHandler(ANDROID_URI, - ATTR_TEXT, newText)); + ATTR_TEXT, newText.length() > 0 ? newText : null)); } } @@ -659,7 +659,7 @@ public class BaseViewRule implements IViewRule { public void onChildInserted(INode node, INode parent, InsertType insertType) { } - private static String stripIdPrefix(String id) { + public static String stripIdPrefix(String id) { if (id.startsWith(NEW_ID_PREFIX)) { id = id.substring(NEW_ID_PREFIX.length()); } else if (id.startsWith(ID_PREFIX)) { @@ -674,21 +674,4 @@ public class BaseViewRule implements IViewRule { } return value; } - - private static class PropertySettingNodeHandler implements INodeHandler { - private final String mNamespaceUri; - private final String mAttribute; - private final String mValue; - - public PropertySettingNodeHandler(String namespaceUri, String attribute, String value) { - super(); - mNamespaceUri = namespaceUri; - mAttribute = attribute; - mValue = value; - } - - public void handle(INode node) { - node.setAttribute(mNamespaceUri, mAttribute, mValue); - } - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/CalendarViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/CalendarViewRule.java new file mode 100644 index 0000000..161cca8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/CalendarViewRule.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.layout; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; + +import com.android.ide.common.api.INode; +import com.android.ide.common.api.IViewRule; +import com.android.ide.common.api.InsertType; + +/** + * An {@link IViewRule} for android.widget.CalendarView. + */ +public class CalendarViewRule extends BaseViewRule { + + @Override + public void onCreate(INode node, INode parent, InsertType insertType) { + super.onCreate(node, parent, insertType); + + // CalendarViews need a lot of space, and the wrapping doesn't seem to work + // well anyway; it reports a much-to-small size than actually accommodates its + // content. + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, getFillParentValueName()); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, getFillParentValueName()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java index b3cf5ce..9aa476e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java @@ -37,7 +37,7 @@ public class DialerFilterRule extends BaseViewRule { super.onCreate(node, parent, insertType); // A DialerFilter requires a couple of nested EditTexts with fixed ids: - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { String fillParent = getFillParentValueName(); INode hint = node.appendChild(FQCN_EDIT_TEXT); hint.setAttribute(ANDROID_URI, ATTR_TEXT, "Hint"); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java new file mode 100644 index 0000000..dfe9d6f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2010 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 com.android.ide.common.layout; + +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.REQUEST_FOCUS; + +import com.android.ide.common.api.IMenuCallback; +import com.android.ide.common.api.INode; +import com.android.ide.common.api.INodeHandler; +import com.android.ide.common.api.IViewRule; +import com.android.ide.common.api.InsertType; +import com.android.ide.common.api.MenuAction; + +import java.util.List; + +/** + * An {@link IViewRule} for android.widget.EditText. + */ +public class EditTextRule extends BaseViewRule { + + @Override + public void onCreate(INode node, INode parent, InsertType insertType) { + super.onCreate(node, parent, insertType); + + if (parent != null) { + INode focus = findFocus(findRoot(parent)); + if (focus == null) { + // Add <requestFocus> + node.appendChild(REQUEST_FOCUS); + } + } + } + + /** + * {@inheritDoc} + * <p> + * Adds a "Request Focus" menu item. + */ + @Override + public List<MenuAction> getContextMenu(final INode selectedNode) { + final boolean hasFocus = hasFocus(selectedNode); + final String label = hasFocus ? "Clear Focus" : "Request Focus"; + + IMenuCallback onChange = new IMenuCallback() { + public void action(MenuAction menuAction, String valueId, Boolean newValue) { + selectedNode.editXml(label, new INodeHandler() { + public void handle(INode node) { + INode focus = findFocus(findRoot(node)); + if (focus != null && focus.getParent() != null) { + focus.getParent().removeChild(focus); + } + if (!hasFocus) { + node.appendChild(REQUEST_FOCUS); + } + } + }); + } + }; + + return concatenate(super.getContextMenu(selectedNode), + new MenuAction.Action("_setfocus", label, null, onChange)); //$NON-NLS-1$ + } + + /** Returns true if the given node currently has focus */ + private static boolean hasFocus(INode node) { + INode focus = findFocus(node); + if (focus != null) { + return focus.getParent() == node; + } + + return false; + } + + /** Returns the root/top level node in the view hierarchy that contains the given node */ + private static INode findRoot(INode node) { + // First find the parent + INode root = node; + while (root != null) { + INode parent = root.getParent(); + if (parent == null) { + break; + } else { + root = parent; + } + } + + return root; + } + + /** Finds the focus node (not the node containing focus, but the actual request focus node + * under a given node */ + private static INode findFocus(INode node) { + if (node.getFqcn().equals(REQUEST_FOCUS)) { + return node; + } + + for (INode child : node.getChildren()) { + INode focus = findFocus(child); + if (focus != null) { + return focus; + } + } + return null; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java index 655eee2..b8c7408 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java @@ -16,7 +16,10 @@ package com.android.ide.common.layout; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_GRAVITY; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import com.android.ide.common.api.DrawingStyle; import com.android.ide.common.api.DropFeedback; @@ -25,10 +28,13 @@ import com.android.ide.common.api.IFeedbackPainter; import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.INodeHandler; +import com.android.ide.common.api.IViewMetadata; import com.android.ide.common.api.IViewRule; +import com.android.ide.common.api.InsertType; import com.android.ide.common.api.MenuAction; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; +import com.android.ide.common.api.IViewMetadata.FillPreference; import com.android.util.Pair; import java.util.List; @@ -56,7 +62,7 @@ public class FrameLayoutRule extends BaseLayoutRule { }); } - void drawFeedback( + protected void drawFeedback( IGraphics gc, INode targetNode, IDragElement[] elements, @@ -155,8 +161,25 @@ public class FrameLayoutRule extends BaseLayoutRule { super.addLayoutActions(actions, parentNode, children); actions.add(MenuAction.createSeparator(25)); actions.add(createMarginAction(parentNode, children)); - if (children.size() > 0) { + if (children != null && children.size() > 0) { actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY)); } } + + @Override + public void onChildInserted(INode node, INode parent, InsertType insertType) { + // Look at the fill preferences and fill embedded layouts etc + String fqcn = node.getFqcn(); + IViewMetadata metadata = mRulesEngine.getMetadata(fqcn); + if (metadata != null) { + FillPreference fill = metadata.getFillPreference(); + String fillParent = getFillParentValueName(); + if (fill.fillHorizontally(true)) { + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); + } + if (fill.fillVertically(false)) { + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java index 71bd704..62ea6f9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java @@ -23,9 +23,15 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; import static com.android.ide.common.layout.LayoutConstants.FQCN_LINEAR_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.VALUE_HORIZONTAL; +import com.android.ide.common.api.DrawingStyle; +import com.android.ide.common.api.DropFeedback; +import com.android.ide.common.api.IDragElement; +import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; +import com.android.ide.common.api.Point; +import com.android.ide.common.api.Rect; /** * An {@link IViewRule} for android.widget.HorizontalScrollView. @@ -46,7 +52,7 @@ public class HorizontalScrollViewRule extends FrameLayoutRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { // Insert a horizontal linear layout which is commonly used with horizontal scrollbars // as described by the documentation for HorizontalScrollbars. INode linearLayout = node.appendChild(FQCN_LINEAR_LAYOUT); @@ -55,4 +61,32 @@ public class HorizontalScrollViewRule extends FrameLayoutRule { } } + @Override + public DropFeedback onDropMove(INode targetNode, IDragElement[] elements, + DropFeedback feedback, Point p) { + DropFeedback f = super.onDropMove(targetNode, elements, feedback, p); + + // HorizontalScrollViews only allow a single child + if (targetNode.getChildren().length > 0) { + f.invalidTarget = true; + } + return f; + } + + @Override + protected void drawFeedback( + IGraphics gc, + INode targetNode, + IDragElement[] elements, + DropFeedback feedback) { + if (targetNode.getChildren().length > 0) { + Rect b = targetNode.getBounds(); + if (b.isValid()) { + gc.useStyle(DrawingStyle.DROP_RECIPIENT); + gc.drawRect(b); + } + } else { + super.drawFeedback(gc, targetNode, elements, feedback); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java index 4338a9a..6fceba4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java @@ -24,7 +24,7 @@ import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; /** - * An {@link IViewRule} for android.widget.ImageButtonRule. + * An {@link IViewRule} for android.widget.ImageButton. */ public class ImageButtonRule extends BaseViewRule { @@ -32,7 +32,19 @@ public class ImageButtonRule extends BaseViewRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + // When dropping an include tag, ask the user which layout to include. + if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW + String src = mRulesEngine.displayResourceInput("drawable", ""); //$NON-NLS-1$ //$NON-NLS-2$ + if (src != null) { + node.editXml("Set Image", + new PropertySettingNodeHandler(ANDROID_URI, ATTR_SRC, + src.length() > 0 ? src : null)); + return; + } + } + + // Fallback if dismissed or during previews etc + if (insertType.isCreate()) { node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc()); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java index 9d26e75..a33323e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java @@ -32,7 +32,19 @@ public class ImageViewRule extends BaseViewRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + // When dropping an include tag, ask the user which layout to include. + if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW + String src = mRulesEngine.displayResourceInput("drawable", ""); //$NON-NLS-1$ //$NON-NLS-2$ + if (src != null) { + node.editXml("Set Image", + new PropertySettingNodeHandler(ANDROID_URI, ATTR_SRC, + src.length() > 0 ? src : null)); + return; + } + } + + // Fallback if dismissed or during previews etc + if (insertType.isCreate()) { node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc()); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java new file mode 100644 index 0000000..a451257 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 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 com.android.ide.common.layout; + +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.ATTR_LAYOUT; + +import com.android.ide.common.api.INode; +import com.android.ide.common.api.IViewRule; +import com.android.ide.common.api.InsertType; + +/** + * An {@link IViewRule} for the special XML {@code <include>} tag. + */ +public class IncludeRule extends BaseViewRule { + @Override + public void onCreate(INode node, INode parent, InsertType insertType) { + // When dropping an include tag, ask the user which layout to include. + if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW + String include = mRulesEngine.displayIncludeSourceInput(); + if (include != null) { + node.editXml("Include Layout", + // Note -- the layout attribute is NOT in the Android namespace! + new PropertySettingNodeHandler(null, ATTR_LAYOUT, + include.length() > 0 ? include : null)); + } else { + // Remove the view; the insertion was canceled + parent.removeChild(node); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index ee5a8c9..bb04498 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -16,6 +16,8 @@ package com.android.ide.common.layout; +import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; + import com.android.sdklib.SdkConstants; /** @@ -27,10 +29,10 @@ import com.android.sdklib.SdkConstants; * </ul> */ public class LayoutConstants { - /** The element name in a <code><view class="..."></code> element. */ + /** The element name in a {@code <view class="...">} element. */ public static final String VIEW = "view"; //$NON-NLS-1$ - /** The attribute name in a <code><view class="..."></code> element. */ + /** The attribute name in a {@code <view class="...">} element. */ public static final String ATTR_CLASS = "class"; //$NON-NLS-1$ public static final String ATTR_ON_CLICK = "onClick"; //$NON-NLS-1$ @@ -38,17 +40,28 @@ public class LayoutConstants { public static final String RELATIVE_LAYOUT = "RelativeLayout"; //$NON-NLS-1$ public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$ public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; //$NON-NLS-1$ + public static final String TABLE_LAYOUT = "TableLayout"; //$NON-NLS-1$ + public static final String TABLE_ROW = "TableRow"; //$NON-NLS-1$ + public static final String CALENDAR_VIEW = "CalendarView"; //$NON-NLS-1$ public static final String LIST_VIEW = "ListView"; //$NON-NLS-1$ + public static final String EDIT_TEXT = "EditText"; //$NON-NLS-1$ public static final String GALLERY = "Gallery"; //$NON-NLS-1$ public static final String GRID_VIEW = "GridView"; //$NON-NLS-1$ public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$ + public static final String RADIO_BUTTON = "RadioButton"; //$NON-NLS-1$ + public static final String RADIO_GROUP = "RadioGroup"; //$NON-NLS-1$ public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView";//$NON-NLS-1$ + public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView";//$NON-NLS-1$ + public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$ public static final String ATTR_TEXT = "text"; //$NON-NLS-1$ + public static final String ATTR_HINT = "hint"; //$NON-NLS-1$ public static final String ATTR_ID = "id"; //$NON-NLS-1$ + public static final String ATTR_STYLE = "style"; //$NON-NLS-1$ public static final String ATTR_HANDLE = "handle"; //$NON-NLS-1$ public static final String ATTR_CONTENT = "content"; //$NON-NLS-1$ public static final String ATTR_CHECKED = "checked"; //$NON-NLS-1$ + public static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_PREFIX = "layout_"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$ @@ -62,10 +75,16 @@ public class LayoutConstants { public static final String ATTR_LAYOUT_MARGIN_TOP = "layout_marginTop"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_MARGIN_BOTTOM = "layout_marginBottom"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_LEFT = "layout_alignLeft"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_RIGHT = "layout_alignRight"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_TOP = "layout_alignTop"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_BOTTOM = "layout_alignBottom"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = "layout_alignParentBottom"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_ALIGN_PARENT_LEFT = "layout_alignParentLeft";//$NON-NLS-1$ public static final String ATTR_LAYOUT_ALIGN_PARENT_RIGHT = "layout_alignParentRight"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING = "layout_alignWithParentMissing"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_ALIGN_BASELINE = "layout_alignBaseline"; //$NON-NLS-1$ @@ -84,7 +103,8 @@ public class LayoutConstants { public static final String VALUE_WRAP_CONTENT = "wrap_content"; //$NON-NLS-1$ public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$ public static final String VALUE_TRUE = "true"; //$NON-NLS-1$ - public static final String VALUE_N_DIP = "%ddip"; //$NON-NLS-1$ + public static final String VALUE_FALSE= "false"; //$NON-NLS-1$ + public static final String VALUE_N_DP = "%ddp"; //$NON-NLS-1$ public static final String VALUE_CENTER_VERTICAL = "centerVertical"; //$NON-NLS-1$ public static final String VALUE_CENTER_IN_PARENT = "centerInParent"; //$NON-NLS-1$ @@ -105,8 +125,25 @@ public class LayoutConstants { public static final String VALUE_ALIGN_WITH_PARENT_MISSING = "alignWithParentMissing"; //$NON-NLS-1$ + // Gravity values. These have the GRAVITY_ prefix in front of value because we already + // have VALUE_CENTER_HORIZONTAL defined for layouts, and its definition conflicts + // (centerHorizontal versus center_horizontal) + public static final String GRAVITY_VALUE_ = "center"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_CENTER = "center"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_RIGHT = "right"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_LEFT = "left"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_BOTTOM = "bottom"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_TOP = "top"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$ + public static final String GRAVITY_VALUE_FILL = "fill"; //$NON-NLS-1$ + /** The default prefix used for the {@link #ANDROID_URI} name space */ - public static final String ANDROID_NS_PREFIX = "android"; //$NON-NLS-1$ + public static final String ANDROID_NS_NAME = "android"; //$NON-NLS-1$ + /** The default prefix used for the {@link #ANDROID_URI} name space including the colon */ + public static final String ANDROID_NS_NAME_PREFIX = "android:"; //$NON-NLS-1$ /** * Namespace for the Android resource XML, i.e. @@ -114,18 +151,35 @@ public class LayoutConstants { */ public static final String ANDROID_URI = SdkConstants.NS_RESOURCES; + /** + * The package name where the widgets live (the ones that require no prefix in layout + * files) + */ + public static final String ANDROID_WIDGET_PREFIX = "android.widget."; //$NON-NLS-1$ + + /** + * The top level android package as a prefix, "android.". + */ + public static final String ANDROID_PKG_PREFIX = ANDROID_PKG + '.'; + /** The fully qualified class name of an EditText view */ public static final String FQCN_EDIT_TEXT = "android.widget.EditText"; //$NON-NLS-1$ /** The fully qualified class name of a LinearLayout view */ public static final String FQCN_LINEAR_LAYOUT = "android.widget.LinearLayout"; //$NON-NLS-1$ + /** The fully qualified class name of a RelativeLayout view */ + public static final String FQCN_RELATIVE_LAYOUT = "android.widget.RelativeLayout"; //$NON-NLS-1$ + /** The fully qualified class name of a FrameLayout view */ public static final String FQCN_FRAME_LAYOUT = "android.widget.FrameLayout"; //$NON-NLS-1$ /** The fully qualified class name of a TableRow view */ public static final String FQCN_TABLE_ROW = "android.widget.TableRow"; //$NON-NLS-1$ + /** The fully qualified class name of a TableLayout view */ + public static final String FQCN_TABLE_LAYOUT = "android.widget.TableLayout"; //$NON-NLS-1$ + /** The fully qualified class name of a TabWidget view */ public static final String FQCN_TAB_WIDGET = "android.widget.TabWidget"; //$NON-NLS-1$ @@ -138,6 +192,12 @@ public class LayoutConstants { /** The fully qualified class name of an AdapterView */ public static final String FQCN_ADAPTER_VIEW = "android.widget.AdapterView"; //$NON-NLS-1$ + /** The fully qualified class name of a GestureOverlayView */ + public static final String FQCN_GESTURE_OVERLAY_VIEW = "android.gesture.GestureOverlayView"; //$NON-NLS-1$ + + /** The fully qualified class name of a RadioGroup */ + public static final String FQCN_RADIO_GROUP = "android.widgets.RadioGroup"; //$NON-NLS-1$ + public static final String ATTR_SRC = "src"; //$NON-NLS-1$ // like fill_parent for API 8 @@ -169,4 +229,7 @@ public class LayoutConstants { /** Prefix for resources that reference Android strings */ public static String ANDROID_STRING_PREFIX = "@android:string/"; //$NON-NLS-1$ + + /** Prefix for resources that reference Android layouts */ + public static String ANDROID_LAYOUT_PREFIX = "@android:layout/"; //$NON-NLS-1$ } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java index 1e2d0e3..4b9d006 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java @@ -530,6 +530,11 @@ public class LinearLayoutRule extends BaseLayoutRule { String fillParent = getFillParentValueName(); if (fill.fillHorizontally(vertical)) { node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); + } else if (!vertical && fill == FillPreference.WIDTH_IN_VERTICAL) { + // In a horizontal layout, make views that would fill horizontally in a + // vertical layout have a non-zero weight instead. This will make the item + // fill but only enough to allow other views to be shown as well. + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, "1"); //$NON-NLS-1$ } if (fill.fillVertically(vertical)) { node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java index aee94bc..c7e21c6 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java @@ -24,7 +24,8 @@ import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; /** - * An {@link IViewRule} for android.widget.ListView and all its derived classes. + * An {@link IViewRule} for android.widget.ListView and all its derived classes such + * as ExpandableListView. * This is the "root" rule, that is used whenever there is not more specific * rule to apply. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java index 301385a..eb1cf47 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java @@ -34,7 +34,7 @@ public class MapViewRule extends BaseViewRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { node.setAttribute(ANDROID_URI, "android:apiKey", //$NON-NLS-1$ "Your API key: see " + //$NON-NLS-1$ "http://code.google.com/android/add-ons/google-apis/mapkey.html"); //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java new file mode 100644 index 0000000..8c57da8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 com.android.ide.common.layout; + +import com.android.ide.common.api.INode; +import com.android.ide.common.api.INodeHandler; + +/** + * A convenience implementation of {@link INodeHandler} for setting a given attribute to a + * given value on a particular node. + */ +class PropertySettingNodeHandler implements INodeHandler { + private final String mNamespaceUri; + private final String mAttribute; + private final String mValue; + + PropertySettingNodeHandler(String namespaceUri, String attribute, String value) { + super(); + mNamespaceUri = namespaceUri; + mAttribute = attribute; + mValue = value; + } + + public void handle(INode node) { + node.setAttribute(mNamespaceUri, mAttribute, mValue); + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java index 039b495..88cce52 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java @@ -33,7 +33,7 @@ public class RadioGroupRule extends LinearLayoutRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { for (int i = 0; i < 3; i++) { INode handle = node.appendChild(LayoutConstants.FQCN_RADIO_BUTTON); handle.setAttribute(ANDROID_URI, ATTR_ID, String.format("@+id/radio%d", i)); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java index e3c349a..385dcc5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java @@ -21,9 +21,15 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.FQCN_LINEAR_LAYOUT; +import com.android.ide.common.api.DrawingStyle; +import com.android.ide.common.api.DropFeedback; +import com.android.ide.common.api.IDragElement; +import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; +import com.android.ide.common.api.Point; +import com.android.ide.common.api.Rect; /** * An {@link IViewRule} for android.widget.ScrollView. @@ -44,7 +50,7 @@ public class ScrollViewRule extends FrameLayoutRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { // Insert a default linear layout (which will in turn be registered as // a child of this node and the create child method above will set its // fill parent attributes, its id, etc. @@ -52,4 +58,32 @@ public class ScrollViewRule extends FrameLayoutRule { } } + @Override + public DropFeedback onDropMove(INode targetNode, IDragElement[] elements, + DropFeedback feedback, Point p) { + DropFeedback f = super.onDropMove(targetNode, elements, feedback, p); + + // ScrollViews only allow a single child + if (targetNode.getChildren().length > 0) { + f.invalidTarget = true; + } + return f; + } + + @Override + protected void drawFeedback( + IGraphics gc, + INode targetNode, + IDragElement[] elements, + DropFeedback feedback) { + if (targetNode.getChildren().length > 0) { + Rect b = targetNode.getBounds(); + if (b.isValid()) { + gc.useStyle(DrawingStyle.DROP_RECIPIENT); + gc.drawRect(b); + } + } else { + super.drawFeedback(gc, targetNode, elements, feedback); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java index 15c3b4c..4af0ae9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java @@ -37,7 +37,7 @@ public class SlidingDrawerRule extends BaseLayoutRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { String matchParent = getFillParentValueName(); node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent); node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java index 92decde..2d7625b 100755..100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java @@ -43,7 +43,7 @@ public class TabHostRule extends IgnoredLayoutRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { String fillParent = getFillParentValueName(); // Configure default Table setup as described in the Table tutorial diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java index 6ac670f..cc67d3a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java @@ -17,13 +17,19 @@ package com.android.ide.common.layout; import static com.android.ide.common.layout.LayoutConstants.FQCN_TABLE_ROW; +import com.android.ide.common.api.IClientRulesEngine; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; +import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; import com.android.ide.common.api.MenuAction; +import java.net.URL; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * An {@link IViewRule} for android.widget.TableLayout. @@ -33,6 +39,13 @@ public class TableLayoutRule extends LinearLayoutRule { // the default is vertical, not horizontal // The fill of all children should be wrap_content + private static final String ACTION_ADD_ROW = "_addrow"; //$NON-NLS-1$ + private static final String ACTION_REMOVE_ROW = "_removerow"; //$NON-NLS-1$ + private static final URL ICON_ADD_ROW = + TableLayoutRule.class.getResource("addrow.png"); //$NON-NLS-1$ + private static final URL ICON_REMOVE_ROW = + TableLayoutRule.class.getResource("removerow.png"); //$NON-NLS-1$ + @Override protected boolean isVertical(INode node) { // Tables are always vertical @@ -58,8 +71,8 @@ public class TableLayoutRule extends LinearLayoutRule { IMenuCallback addTab = new IMenuCallback() { public void action(MenuAction action, final String valueId, Boolean newValue) { final INode node = selectedNode; - node.appendChild(FQCN_TABLE_ROW); - + INode newRow = node.appendChild(FQCN_TABLE_ROW); + mRulesEngine.select(Collections.singletonList(newRow)); } }; return concatenate(super.getContextMenu(selectedNode), @@ -67,4 +80,99 @@ public class TableLayoutRule extends LinearLayoutRule { null, addTab)); } + @Override + public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + addTableLayoutActions(mRulesEngine, actions, parentNode, children); + } + + /** + * Adds layout actions to add and remove toolbar items + */ + static void addTableLayoutActions(final IClientRulesEngine rulesEngine, + List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + IMenuCallback actionCallback = new IMenuCallback() { + public void action(final MenuAction action, final String valueId, + final Boolean newValue) { + parentNode.editXml("Add/Remove Table Row", new INodeHandler() { + public void handle(INode n) { + if (action.getId().equals(ACTION_ADD_ROW)) { + // Determine the index of the selection, if any; if there is + // a selection, insert the row before the current row, otherwise + // append it to the table. + int index = -1; + INode[] rows = parentNode.getChildren(); + if (children != null) { + findTableIndex: + for (INode child : children) { + // Find direct child of table layout + while (child != null && child.getParent() != parentNode) { + child = child.getParent(); + } + if (child != null) { + // Compute index of direct child of table layout + for (int i = 0; i < rows.length; i++) { + if (rows[i] == child) { + index = i; + break findTableIndex; + } + } + } + } + } + INode newRow; + if (index == -1) { + newRow = parentNode.appendChild(FQCN_TABLE_ROW); + } else { + newRow = parentNode.insertChildAt(FQCN_TABLE_ROW, index); + } + rulesEngine.select(Collections.singletonList(newRow)); + } else if (action.getId().equals(ACTION_REMOVE_ROW)) { + // Find the direct children of the TableLayout to delete; + // this is necessary since TableRow might also use + // this implementation, so the parentNode is the true + // TableLayout but the children might be grand children. + Set<INode> targets = new HashSet<INode>(); + for (INode child : children) { + while (child != null && child.getParent() != parentNode) { + child = child.getParent(); + } + if (child != null) { + targets.add(child); + } + } + for (INode target : targets) { + parentNode.removeChild(target); + } + } + } + }); + } + }; + + // Add Row + actions.add(MenuAction.createSeparator(150)); + actions.add(MenuAction.createAction(ACTION_ADD_ROW, "Add Table Row", null, actionCallback, + ICON_ADD_ROW, 160)); + + // Remove Row (if something is selected) + if (children != null && children.size() > 0) { + actions.add(MenuAction.createAction(ACTION_REMOVE_ROW, "Remove Table Row", null, + actionCallback, ICON_REMOVE_ROW, 170)); + } + } + + @Override + public void onCreate(INode node, INode parent, InsertType insertType) { + super.onCreate(node, parent, insertType); + + if (insertType.isCreate()) { + // Start the table with 4 rows + for (int i = 0; i < 4; i++) { + node.appendChild(FQCN_TABLE_ROW); + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java index e734f4a..ac03653 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java @@ -15,9 +15,14 @@ */ package com.android.ide.common.layout; +import static com.android.ide.common.layout.LayoutConstants.FQCN_TABLE_LAYOUT; + import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; +import com.android.ide.common.api.MenuAction; + +import java.util.List; /** * An {@link IViewRule} for android.widget.TableRow. @@ -40,4 +45,20 @@ public class TableRowRule extends LinearLayoutRule { // respectively. } + @Override + public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + + // Also apply table-specific actions on the table row such that you can + // select something in a table row and still get offered actions on the surrounding + // table. + if (children != null) { + INode grandParent = parentNode.getParent(); + if (grandParent != null && grandParent.getFqcn().equals(FQCN_TABLE_LAYOUT)) { + TableLayoutRule.addTableLayoutActions(mRulesEngine, actions, grandParent, + children); + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java index 8ec53db..00085c8 100755..100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java @@ -35,7 +35,7 @@ public class WebViewRule extends IgnoredLayoutRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { String matchParent = getFillParentValueName(); node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent); node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java index ca0413e..1200da9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java @@ -26,7 +26,7 @@ public class ZoomButtonRule extends BaseViewRule { public void onCreate(INode node, INode parent, InsertType insertType) { super.onCreate(node, parent, insertType); - if (insertType == InsertType.CREATE) { + if (insertType.isCreate()) { node.setAttribute(ANDROID_URI, ATTR_SRC, "@android:drawable/btn_plus"); //$NON-NLS-1$ } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addrow.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addrow.png Binary files differnew file mode 100644 index 0000000..0faa3e6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addrow.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removerow.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removerow.png Binary files differnew file mode 100644 index 0000000..db695a7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removerow.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java index bd79e29..1e14f9f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttrsXmlParser.java @@ -256,7 +256,7 @@ public final class AttrsXmlParser { if (parents != null) { String[] parentsArray = parseStyleableParents(parents, mStyleMap, unknownParents); - style.setParents(parentsArray); //$NON-NLS-1$ + style.setParents(parentsArray); } mStyleMap.put(name, style); unknownParents.remove(name); @@ -441,9 +441,9 @@ public final class AttrsXmlParser { * </ul> * The format may be one type or two types (e.g. "reference|color"). * An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute, - * they are implicitely stated by the presence of sub-nodes <enum> or <flag>. + * they are implicitly stated by the presence of sub-nodes <enum> or <flag>. * <p/> - * By design, <attr> nodes of the same name MUST have the same type. + * By design, attr nodes of the same name MUST have the same type. * Attribute nodes are thus cached by name and reused as much as possible. * When reusing a node, it is duplicated and its javadoc reassigned. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java index 85e265c..e7c5157 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt; +import com.android.AndroidConstants; import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder; import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder; import com.android.ide.eclipse.adt.internal.build.builders.ResourceManagerBuilder; @@ -43,7 +44,7 @@ import java.util.regex.Pattern; * </ul> * */ -public class AndroidConstants { +public class AdtConstants { /** * The old Editors Plugin ID. It is still used in some places for compatibility. * Please do not use for new features. @@ -144,7 +145,7 @@ public class AndroidConstants { public final static String WS_ASSETS = WS_SEP + SdkConstants.FD_ASSETS; /** Absolute path of the layout folder, e.g. "/res/layout".<br> This is a workspace path. */ - public final static String WS_LAYOUTS = WS_RESOURCES + WS_SEP + SdkConstants.FD_LAYOUT; + public final static String WS_LAYOUTS = WS_RESOURCES + WS_SEP + AndroidConstants.FD_RES_LAYOUT; /** Leaf of the javaDoc folder. Does not start with a separator. */ public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/" + //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index d7f2e3c..55a5ef8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -19,9 +19,15 @@ package com.android.ide.eclipse.adt; import com.android.ddmuilib.console.DdmConsole; import com.android.ddmuilib.console.IDdmConsole; import com.android.ide.common.log.ILogger; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.internal.VersionCheck; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimationEditor; +import com.android.ide.eclipse.adt.internal.editors.color.ColorEditor; +import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableEditor; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder; import com.android.ide.eclipse.adt.internal.editors.menu.MenuEditor; @@ -34,18 +40,16 @@ import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper; import com.android.ide.eclipse.ddms.DdmsPlugin; +import com.android.io.StreamException; +import com.android.resources.ResourceFolderType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.StreamException; import com.android.sdkstats.SdkStatsService; import org.eclipse.core.resources.IFile; @@ -64,10 +68,14 @@ import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.graphics.Color; @@ -79,7 +87,10 @@ import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.browser.IWebBrowser; +import org.eclipse.ui.browser.IWorkbenchBrowserSupport; import org.eclipse.ui.console.ConsolePlugin; import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleConstants; @@ -114,6 +125,15 @@ import java.util.List; * The activator class controls the plug-in life cycle */ public class AdtPlugin extends AbstractUIPlugin implements ILogger { + /** + * Temporary logging code to help track down + * http://code.google.com/p/android/issues/detail?id=15003 + * + * Deactivated right now. + * TODO remove this and associated logging code once we're done with issue 15003. + */ + public static final boolean DEBUG_XML_FILE_INIT = false; + /** The plug-in ID */ public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$ @@ -354,7 +374,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { /** Returns the absolute traceview path */ public static String getOsAbsoluteTraceview() { return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + - AndroidConstants.FN_TRACEVIEW; + AdtConstants.FN_TRACEVIEW; } /** Returns the absolute emulator path */ @@ -364,7 +384,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { public static String getOsAbsoluteHprofConv() { return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + - AndroidConstants.FN_HPROF_CONV; + AdtConstants.FN_HPROF_CONV; } /** Returns the absolute proguard path */ @@ -377,7 +397,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { */ public static String getUrlDoc() { return ProjectHelper.getJavaDocPath( - getOsSdkFolder() + AndroidConstants.WS_JAVADOC_FOLDER_LEAF); + getOsSdkFolder() + AdtConstants.WS_JAVADOC_FOLDER_LEAF); } /** @@ -778,7 +798,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { public static InputStream readEmbeddedFileAsStream(String filepath) { // attempt to read an embedded file try { - URL url = getEmbeddedFileUrl(AndroidConstants.WS_SEP + filepath); + URL url = getEmbeddedFileUrl(AdtConstants.WS_SEP + filepath); if (url != null) { return url.openStream(); } @@ -811,8 +831,8 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { // attempt to get a file to one of the template. String path = filepath; - if (!path.startsWith(AndroidConstants.WS_SEP)) { - path = AndroidConstants.WS_SEP + path; + if (!path.startsWith(AdtConstants.WS_SEP)) { + path = AdtConstants.WS_SEP + path; } URL url = bundle.getEntry(path); @@ -1148,7 +1168,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { // not meant to be exhaustive. String[] filesToCheck = new String[] { osSdkLocation + getOsRelativeAdb(), - osSdkLocation + getOsRelativeEmulator() + osSdkLocation + getOsRelativeEmulator() + SdkConstants.FN_EMULATOR_EXTENSION }; for (String file : filesToCheck) { if (checkFile(file) == false) { @@ -1416,10 +1436,15 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { * @see IFileListener#fileChanged */ public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) { + if (AdtConstants.EXT_XML.equals(file.getFileExtension())) { // The resources files must have a file path similar to // project/res/.../*.xml // There is no support for sub folders, so the segment count must be 4 + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, "fileChanged %1$s", + file.getFullPath().toOSString()); + } + if (file.getFullPath().segmentCount() == 4) { // check if we are inside the res folder. String segment = file.getFullPath().segment(1); @@ -1449,34 +1474,67 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { resourceChanged(file, resFolder.getType()); } } else { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " The resource folder was null"); + } + // if the res folder is null, this means the name is invalid, // in this case we remove whatever android editors that was set // as the default editor. IEditorDescriptor desc = IDE.getDefaultEditor(file); String editorId = desc.getId(); - if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, "Old editor id=%1$s", editorId); + } + if (editorId.startsWith(AdtConstants.EDITORS_NAMESPACE)) { // reset the default editor. IDE.setDefaultEditor(file, null); + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " Resetting editor id to default"); + } } } + } else { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " Not in resources/, ignoring"); + } } } } } private void resourceAdded(IFile file, ResourceFolderType type) { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, "resourceAdded %1$s - type=%1$s", + file.getFullPath().toOSString(), type); + } // set the default editor based on the type. if (type == ResourceFolderType.LAYOUT) { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " set default editor id to layout"); + } IDE.setDefaultEditor(file, LayoutEditor.ID); - } else if (type == ResourceFolderType.DRAWABLE - || type == ResourceFolderType.VALUES) { + } else if (type == ResourceFolderType.VALUES) { IDE.setDefaultEditor(file, ResourcesEditor.ID); } else if (type == ResourceFolderType.MENU) { IDE.setDefaultEditor(file, MenuEditor.ID); + } else if (type == ResourceFolderType.COLOR) { + IDE.setDefaultEditor(file, ColorEditor.ID); + } else if (type == ResourceFolderType.DRAWABLE) { + IDE.setDefaultEditor(file, DrawableEditor.ID); + } else if (type == ResourceFolderType.ANIMATOR + || type == ResourceFolderType.ANIM) { + IDE.setDefaultEditor(file, AnimationEditor.ID); } else if (type == ResourceFolderType.XML) { if (XmlEditor.canHandleFile(file)) { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " set default editor id to XmlEditor.id"); + } IDE.setDefaultEditor(file, XmlEditor.ID); } else { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " set default editor id unknown"); + } // set a property to determine later if the XML can be handled QualifiedName qname = new QualifiedName( AdtPlugin.PLUGIN_ID, @@ -1487,6 +1545,10 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { } private void resourceChanged(IFile file, ResourceFolderType type) { + if (DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, "resourceChanged %1$s - type=%1$s", + file.getFullPath().toOSString(), type); + } if (type == ResourceFolderType.XML) { IEditorDescriptor ed = IDE.getDefaultEditor(file); if (ed == null || ed.getId() != XmlEditor.ID) { @@ -1707,4 +1769,99 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { public void warning(String format, Object... args) { log(IStatus.WARNING, format, args); } + + /** + * Opens the given URL in a browser tab + * + * @param url the URL to open in a browser + */ + public static void openUrl(URL url) { + IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport(); + IWebBrowser browser; + try { + browser = support.createBrowser(PLUGIN_ID); + browser.openURL(url); + } catch (PartInitException e) { + log(e, null); + } + } + + /** + * Opens a Java class for the given fully qualified class name + * + * @param project the project containing the class + * @param fqcn the fully qualified class name of the class to be opened + * @return true if the class was opened, false otherwise + */ + public static boolean openJavaClass(IProject project, String fqcn) { + if (fqcn == null) { + return false; + } + + // Handle inner classes + if (fqcn.indexOf('$') != -1) { + fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$ + } + + try { + if (project.hasNature(JavaCore.NATURE_ID)) { + IJavaProject javaProject = JavaCore.create(project); + IJavaElement result = javaProject.findType(fqcn); + if (result != null) { + return JavaUI.openInEditor(result) != null; + } + } + } catch (Throwable e) { + log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$ + } + + return false; + } + + /** + * Opens the given file and shows the given (optional) region in the editor (or + * if no region is specified, opens the editor tab.) + * + * @param file the file to be opened + * @param region an optional region which if set will be selected and shown to the + * user + * @throws PartInitException if something goes wrong + */ + public static void openFile(IFile file, IRegion region) throws PartInitException { + openFile(file, region, true); + } + + /** + * Opens the given file and shows the given (optional) region + * + * @param file the file to be opened + * @param region an optional region which if set will be selected and shown to the + * user + * @param showEditorTab if true, front the editor tab after opening the file + * @throws PartInitException if something goes wrong + */ + public static void openFile(IFile file, IRegion region, boolean showEditorTab) + throws PartInitException { + IWorkbench workbench = PlatformUI.getWorkbench(); + if (workbench == null) { + return; + } + IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); + if (activeWorkbenchWindow == null) { + return; + } + IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); + if (page == null) { + return; + } + IEditorPart targetEditor = IDE.openEditor(page, file, true); + if (targetEditor instanceof AndroidXmlEditor) { + AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor; + if (region != null) { + editor.show(region.getOffset(), region.getLength()); + } else if (showEditorTab) { + editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/ConvertToAndroidAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/ConvertToAndroidAction.java index 92604fb..cf46192 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/ConvertToAndroidAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/ConvertToAndroidAction.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.actions; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import org.eclipse.core.resources.IProject; @@ -107,7 +107,7 @@ public class ConvertToAndroidAction implements IObjectActionDelegate { // check if the project already has the android nature. for (int i = 0; i < natures.length; ++i) { - if (AndroidConstants.NATURE_DEFAULT.equals(natures[i])) { + if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) { // we shouldn't be here as the visibility of the item // is dependent on the project. return new Status(Status.WARNING, AdtPlugin.PLUGIN_ID, @@ -121,7 +121,7 @@ public class ConvertToAndroidAction implements IObjectActionDelegate { String[] newNatures = new String[natures.length + 1]; System.arraycopy(natures, 0, newNatures, 1, natures.length); - newNatures[0] = AndroidConstants.NATURE_DEFAULT; + newNatures[0] = AdtConstants.NATURE_DEFAULT; // set the new nature list in the project description.setNatureIds(newNatures); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java index 504cf0b..2a2f0d0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java @@ -16,13 +16,21 @@ package com.android.ide.eclipse.adt.internal.build; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.FindReplaceDocumentAdapter; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.ui.editors.text.TextFileDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProvider; import java.io.File; import java.util.List; @@ -118,6 +126,56 @@ public final class AaptParser { "^(invalid resource directory name): (.*)$"); //$NON-NLS-1$ /** + * Portion of the error message which states the context in which the error occurred, + * such as which property was being processed and what the string value was that + * caused the error. + * <p> + * Example: + * error: No resource found that matches the given name (at 'text' with value '@string/foo') + */ + private static final Pattern sValueRangePattern = + Pattern.compile("\\(at '(.+)' with value '(.*)'\\)"); //$NON-NLS-1$ + + + /** + * Portion of error message which points to the second occurrence of a repeated resource + * definition. + * <p> + * Example: + * error: Resource entry repeatedStyle1 already has bag item android:gravity. + */ + private static final Pattern sRepeatedRangePattern = + Pattern.compile("Resource entry (.+) already has bag item (.+)\\."); //$NON-NLS-1$ + + /** + * Suffix of error message which points to the first occurrence of a repeated resource + * definition. + * Example: + * Originally defined here. + */ + private static final String ORIGINALLY_DEFINED_MSG = "Originally defined here."; //$NON-NLS-1$ + + /** + * Portion of error message which points to the second occurrence of a repeated resource + * definition. + * <p> + * Example: + * error: Resource entry repeatedStyle1 already has bag item android:gravity. + */ + private static final Pattern sNoResourcePattern = + Pattern.compile("No resource found that matches the given name: attr '(.+)'\\."); //$NON-NLS-1$ + + /** + * Portion of error message which points to a missing required attribute in a + * resource definition. + * <p> + * Example: + * error: error: A 'name' attribute is required for <style> + */ + private static final Pattern sRequiredPattern = + Pattern.compile("A '(.+)' attribute is required for <(.+)>"); //$NON-NLS-1$ + + /** * 2 line aapt error<br> * "ERROR: Invalid configuration: foo"<br> * " ^^^"<br> @@ -180,7 +238,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, lineStr, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } continue; @@ -204,7 +262,7 @@ public final class AaptParser { // display the error if (checkAndMark(location, null, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } @@ -228,7 +286,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, lineStr, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } continue; @@ -242,7 +300,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, lineStr, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } @@ -266,7 +324,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, lineStr, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } @@ -282,7 +340,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, lineStr, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { return true; } @@ -298,7 +356,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, lineStr, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } @@ -313,7 +371,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(location, null, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { return true; } @@ -331,7 +389,7 @@ public final class AaptParser { // check the values and attempt to mark the file. if (checkAndMark(null /*location*/, null, msg, osRoot, project, - AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) { + AdtConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) { return true; } @@ -392,12 +450,36 @@ public final class AaptParser { } } + // Attempt to determine the exact range of characters affected by this error. + // This will look up the actual text of the file, go to the particular error line + // and scan for the specific string mentioned in the error. + int startOffset = -1; + int endOffset = -1; + if (f2 instanceof IFile) { + IRegion region = findRange((IFile) f2, line, message); + if (region != null) { + startOffset = region.getOffset(); + endOffset = startOffset + region.getLength(); + } + } + // check if there's a similar marker already, since aapt is launched twice boolean markerAlreadyExists = false; try { IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO); for (IMarker marker : markers) { + if (startOffset != -1) { + int tmpBegin = marker.getAttribute(IMarker.CHAR_START, -1); + if (tmpBegin != startOffset) { + break; + } + int tmpEnd = marker.getAttribute(IMarker.CHAR_END, -1); + if (tmpEnd != startOffset) { + break; + } + } + int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1); if (tmpLine != line) { break; @@ -425,13 +507,159 @@ public final class AaptParser { } if (markerAlreadyExists == false) { - BaseProjectHelper.markResource(f2, markerId, message, line, severity); + BaseProjectHelper.markResource(f2, markerId, message, line, + startOffset, endOffset, severity); } return true; } /** + * Given an aapt error message in a given file and a given (initial) line number, + * return the corresponding offset range for the error, or null. + */ + private static IRegion findRange(IFile file, int line, String message) { + Matcher matcher = sValueRangePattern.matcher(message); + if (matcher.find()) { + String property = matcher.group(1); + String value = matcher.group(2); + + // First find the property. We can't just immediately look for the + // value, because there could be other attributes in this element + // earlier than the one in error, and we might accidentally pick + // up on a different occurrence of the value in a context where + // it is valid. + if (value.length() > 0) { + return findRange(file, line, property, value); + } else { + // Find first occurrence of property followed by '' or "" + IRegion region1 = findRange(file, line, property, "\"\""); //$NON-NLS-1$ + IRegion region2 = findRange(file, line, property, "''"); //$NON-NLS-1$ + if (region1 == null) { + if (region2 == null) { + // Highlight the property instead + return findRange(file, line, property, null); + } + return region2; + } else if (region2 == null) { + return region1; + } else if (region1.getOffset() < region2.getOffset()) { + return region1; + } else { + return region2; + } + } + } + + matcher = sRepeatedRangePattern.matcher(message); + if (matcher.find()) { + String property = matcher.group(2); + return findRange(file, line, property, null); + } + + matcher = sNoResourcePattern.matcher(message); + if (matcher.find()) { + String property = matcher.group(1); + return findRange(file, line, property, null); + } + + matcher = sRequiredPattern.matcher(message); + if (matcher.find()) { + String elementName = matcher.group(2); + IRegion region = findRange(file, line, '<' + elementName, null); + if (region != null && region.getLength() > 1) { + // Skip the opening < + region = new Region(region.getOffset() + 1, region.getLength() - 1); + } + return region; + } + + if (message.endsWith(ORIGINALLY_DEFINED_MSG)) { + return findLineTextRange(file, line); + } + + return null; + } + + /** + * Given a file and line number, return the range of the first match starting on the + * given line. If second is non null, also search for the second string starting at he + * location of the first string. + */ + private static IRegion findRange(IFile file, int line, String first, + String second) { + IRegion region = null; + IDocumentProvider provider = new TextFileDocumentProvider(); + try { + provider.connect(file); + IDocument document = provider.getDocument(file); + if (document != null) { + IRegion lineInfo = document.getLineInformation(line - 1); + int lineStartOffset = lineInfo.getOffset(); + // The aapt errors will be anchored on the line where the + // element starts - which means that with formatting where + // attributes end up on subsequent lines we don't find it on + // the error line indicated by aapt. + // Therefore, search forwards in the document. + FindReplaceDocumentAdapter adapter = + new FindReplaceDocumentAdapter(document); + + region = adapter.find(lineStartOffset, first, + true /*forwardSearch*/, true /*caseSensitive*/, + false /*wholeWord*/, false /*regExSearch*/); + if (region != null && second != null) { + region = adapter.find(region.getOffset() + first.length(), second, + true /*forwardSearch*/, true /*caseSensitive*/, + false /*wholeWord*/, false /*regExSearch*/); + } + } + } catch (Exception e) { + AdtPlugin.log(e, "Can't find range information for %1$s", file.getName()); + } finally { + provider.disconnect(file); + } + return region; + } + + /** Returns the non-whitespace line range at the given line number. */ + private static IRegion findLineTextRange(IFile file, int line) { + IDocumentProvider provider = new TextFileDocumentProvider(); + try { + provider.connect(file); + IDocument document = provider.getDocument(file); + if (document != null) { + IRegion lineInfo = document.getLineInformation(line - 1); + String lineContents = document.get(lineInfo.getOffset(), lineInfo.getLength()); + int lineBegin = 0; + int lineEnd = lineContents.length()-1; + + for (; lineEnd >= 0; lineEnd--) { + char c = lineContents.charAt(lineEnd); + if (!Character.isWhitespace(c)) { + break; + } + } + lineEnd++; + for (; lineBegin < lineEnd; lineBegin++) { + char c = lineContents.charAt(lineBegin); + if (!Character.isWhitespace(c)) { + break; + } + } + if (lineBegin < lineEnd) { + return new Region(lineInfo.getOffset() + lineBegin, lineEnd - lineBegin); + } + } + } catch (Exception e) { + AdtPlugin.log(e, "Can't find range information for %1$s", file.getName()); + } finally { + provider.disconnect(file); + } + + return null; + } + + /** * Returns a matching matcher for the next line * @param lines The array of lines * @param nextIndex The index of the next line diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java new file mode 100644 index 0000000..7d79086 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.build; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; + +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; +import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolution2; +import org.eclipse.ui.IMarkerResolutionGenerator2; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.editors.text.TextFileDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * Shared handler for both quick assist processors (Control key handler) and quick fix + * marker resolution (Problem view handling), since there is a lot of overlap between + * these two UI handlers. + */ +@SuppressWarnings("restriction") // XML model +public class AaptQuickFix implements IMarkerResolutionGenerator2, IQuickAssistProcessor { + + public AaptQuickFix() { + } + + /** Returns the error message from aapt that signals missing resources */ + private static String getTargetMarkerErrorMessage() { + return "No resource found that matches the given name"; + } + + /** Returns the error message from aapt that signals a missing namespace declaration */ + private static String getUnboundErrorMessage() { + return "Error parsing XML: unbound prefix"; + } + + // ---- Implements IMarkerResolution2 ---- + + public boolean hasResolutions(IMarker marker) { + String message = null; + try { + message = (String) marker.getAttribute(IMarker.MESSAGE); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + return message != null + && (message.contains(getTargetMarkerErrorMessage()) + || message.contains(getUnboundErrorMessage())); + } + + public IMarkerResolution[] getResolutions(IMarker marker) { + IResource markerResource = marker.getResource(); + IProject project = markerResource.getProject(); + try { + String message = (String) marker.getAttribute(IMarker.MESSAGE); + if (message.contains(getUnboundErrorMessage()) && markerResource instanceof IFile) { + return new IMarkerResolution[] { + new CreateNamespaceFix((IFile) markerResource) + }; + } + } catch (CoreException e1) { + AdtPlugin.log(e1, null); + } + + int start = marker.getAttribute(IMarker.CHAR_START, 0); + int end = marker.getAttribute(IMarker.CHAR_END, 0); + if (end > start) { + int length = end - start; + IDocumentProvider provider = new TextFileDocumentProvider(); + try { + provider.connect(markerResource); + IDocument document = provider.getDocument(markerResource); + String resource = document.get(start, length); + if (ResourceHelper.canCreateResource(resource)) { + return new IMarkerResolution[] { + new CreateResourceProposal(project, resource) + }; + } + } catch (Exception e) { + AdtPlugin.log(e, "Can't find range information for %1$s", markerResource); + } finally { + provider.disconnect(markerResource); + } + } + + return null; + } + + // ---- Implements IQuickAssistProcessor ---- + + public boolean canAssist(IQuickAssistInvocationContext invocationContext) { + return true; + } + + public boolean canFix(Annotation annotation) { + return true; + } + + public ICompletionProposal[] computeQuickAssistProposals( + IQuickAssistInvocationContext invocationContext) { + + // We have to find the corresponding project/file (so we can look up the aapt + // error markers). Unfortunately, an IQuickAssistProcessor only gets + // access to an ISourceViewer which has no hooks back to the surrounding + // editor. + // + // However, the IQuickAssistProcessor will only be used interactively by a file + // being edited, so we can cheat like the hyperlink detector and simply + // look up the currently active file in the IDE. To be on the safe side, + // we'll make sure that that editor has the same sourceViewer such that + // we are indeed looking at the right file: + ISourceViewer sourceViewer = invocationContext.getSourceViewer(); + AndroidXmlEditor editor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer); + if (editor != null) { + IFile file = editor.getInputFile(); + + try { + IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true, + IResource.DEPTH_ZERO); + + // Look for a match on the same line as the caret. + int offset = invocationContext.getOffset(); + IDocument document = sourceViewer.getDocument(); + IRegion lineInfo = document.getLineInformationOfOffset(offset); + int lineStart = lineInfo.getOffset(); + int lineEnd = lineStart + lineInfo.getLength(); + + for (IMarker marker : markers) { + String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$ + if (message.contains(getTargetMarkerErrorMessage())) { + int start = marker.getAttribute(IMarker.CHAR_START, 0); + int end = marker.getAttribute(IMarker.CHAR_END, 0); + if (start >= lineStart && start <= lineEnd && end > start) { + int length = end - start; + String resource = document.get(start, length); + // Can only offer create value for non-framework value + // resources + if (ResourceHelper.canCreateResource(resource)) { + IProject project = editor.getProject(); + return new ICompletionProposal[] { + new CreateResourceProposal(project, resource) + }; + } + } + } else if (message.contains(getUnboundErrorMessage())) { + return new ICompletionProposal[] { + new CreateNamespaceFix(null) + }; + } + } + } catch (CoreException e) { + AdtPlugin.log(e, null); + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + } + + return null; + } + + public String getErrorMessage() { + return null; + } + + /** Quick fix to insert namespace binding when missing */ + private final static class CreateNamespaceFix + implements ICompletionProposal, IMarkerResolution2 { + private IFile mFile; + + public CreateNamespaceFix(IFile file) { + mFile = file; + } + + private IndexedRegion perform(IDocument doc) { + IModelManager manager = StructuredModelManager.getModelManager(); + IStructuredModel model = manager.getExistingModelForEdit(doc); + if (model != null) { + try { + perform(model); + } finally { + model.releaseFromEdit(); + } + } + + return null; + } + + private IndexedRegion perform(IFile file) { + IModelManager manager = StructuredModelManager.getModelManager(); + IStructuredModel model; + try { + model = manager.getModelForEdit(file); + if (model != null) { + try { + perform(model); + } finally { + model.releaseFromEdit(); + } + } + } catch (Exception e) { + AdtPlugin.log(e, "Can't look up XML model"); + } + + return null; + } + + private IndexedRegion perform(IStructuredModel model) { + if (model instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) model; + Document document = domModel.getDocument(); + Element element = document.getDocumentElement(); + Attr attr = document.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, + "xmlns:android"); //$NON-NLS-1$ + attr.setValue(ANDROID_URI); + element.getAttributes().setNamedItemNS(attr); + return (IndexedRegion) attr; + } + + return null; + } + + // ---- Implements ICompletionProposal ---- + + public void apply(IDocument document) { + perform(document); + } + + public String getAdditionalProposalInfo() { + return "Adds an Android namespace declaratiopn to the root element."; + } + + public IContextInformation getContextInformation() { + return null; + } + + public String getDisplayString() { + return "Insert namespace binding"; + } + + public Image getImage() { + return AdtPlugin.getAndroidLogo(); + } + + public Point getSelection(IDocument doc) { + return null; + } + + + // ---- Implements MarkerResolution2 ---- + + public String getLabel() { + return getDisplayString(); + } + + public void run(IMarker marker) { + try { + AdtPlugin.openFile(mFile, null); + } catch (PartInitException e) { + AdtPlugin.log(e, "Can't open file %1$s", mFile.getName()); + } + + IndexedRegion indexedRegion = perform(mFile); + if (indexedRegion != null) { + try { + IRegion region = + new Region(indexedRegion.getStartOffset(), indexedRegion.getLength()); + AdtPlugin.openFile(mFile, region); + } catch (PartInitException e) { + AdtPlugin.log(e, "Can't open file %1$s", mFile.getName()); + } + } + } + + public String getDescription() { + return getAdditionalProposalInfo(); + } + } + + private static class CreateResourceProposal + implements ICompletionProposal, IMarkerResolution2 { + private final IProject mProject; + private final String mResource; + + CreateResourceProposal(IProject project, String resource) { + super(); + mProject = project; + mResource = resource; + } + + private void perform() { + Pair<ResourceType,String> resource = ResourceHelper.parseResource(mResource); + ResourceType type = resource.getFirst(); + String name = resource.getSecond(); + String value = ""; //$NON-NLS-1$ + + // Try to pick a reasonable first guess. The new value will be highlighted and + // selected for editing, but if we have an initial value then the new file + // won't show an error. + switch (type) { + case STRING: value = "TODO"; break; //$NON-NLS-1$ + case DIMEN: value = "1dp"; break; //$NON-NLS-1$ + case BOOL: value = "true"; break; //$NON-NLS-1$ + case COLOR: value = "#000000"; break; //$NON-NLS-1$ + case INTEGER: value = "1"; break; //$NON-NLS-1$ + case ARRAY: value = "<item>1</item>"; break; //$NON-NLS-1$ + } + + Pair<IFile, IRegion> location = + ResourceHelper.createResource(mProject, type, name, value); + if (location != null) { + IFile file = location.getFirst(); + IRegion region = location.getSecond(); + try { + AdtPlugin.openFile(file, region); + } catch (PartInitException e) { + AdtPlugin.log(e, "Can't open file %1$s", file.getName()); + } + } + } + + // ---- Implements ICompletionProposal ---- + + public void apply(IDocument document) { + perform(); + } + + public String getAdditionalProposalInfo() { + return "Creates an XML file entry for the given missing resource " + + "and opens it in the editor."; + } + + public IContextInformation getContextInformation() { + return null; + } + + public String getDisplayString() { + return String.format("Create resource %1$s", mResource); + } + + public Image getImage() { + return AdtPlugin.getAndroidLogo(); + } + + public Point getSelection(IDocument document) { + return null; + } + + // ---- Implements MarkerResolution2 ---- + + public String getLabel() { + return getDisplayString(); + } + + public void run(IMarker marker) { + perform(); + } + + public String getDescription() { + return getAdditionalProposalInfo(); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java index 9d6a819..09dd571 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.build; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; @@ -81,7 +81,7 @@ public class AidlProcessor extends SourceProcessor { @Override protected String getExtension() { - return AndroidConstants.EXT_AIDL; + return AdtConstants.EXT_AIDL; } @Override @@ -105,13 +105,15 @@ public class AidlProcessor extends SourceProcessor { IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); for (IPath p : sourceFolders) { IFolder f = wsRoot.getFolder(p); - command[index++] = quote("-I" + f.getLocation().toOSString()); //$NON-NLS-1$ + if (f.exists()) { // if the resource doesn't exist, getLocation will return null. + command[index++] = quote("-I" + f.getLocation().toOSString()); //$NON-NLS-1$ + } } boolean verbose = AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE; // remove the generic marker from the project - builder.removeMarkersFromResource(project, AndroidConstants.MARKER_AIDL); + builder.removeMarkersFromResource(project, AdtConstants.MARKER_AIDL); // loop until we've compile them all for (IFile sourceFile : sources) { @@ -127,7 +129,7 @@ public class AidlProcessor extends SourceProcessor { } // Remove the AIDL error markers from the aidl file - builder.removeMarkersFromResource(sourceFile, AndroidConstants.MARKER_AIDL); + builder.removeMarkersFromResource(sourceFile, AdtConstants.MARKER_AIDL); // get the path of the source file. IPath sourcePath = sourceFile.getLocation(); @@ -222,7 +224,7 @@ public class AidlProcessor extends SourceProcessor { AdtPlugin.printErrorToConsole(project, results.toArray()); // mark the project - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_AIDL, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, Messages.Unparsed_AIDL_Errors, IMarker.SEVERITY_ERROR); } else { AdtPlugin.printToConsole(project, results.toArray()); @@ -233,13 +235,13 @@ public class AidlProcessor extends SourceProcessor { } catch (IOException e) { // mark the project and exit String msg = String.format(Messages.AIDL_Exec_Error, command[0]); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_AIDL, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg, IMarker.SEVERITY_ERROR); return false; } catch (InterruptedException e) { // mark the project and exit String msg = String.format(Messages.AIDL_Exec_Error, command[0]); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_AIDL, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_AIDL, msg, IMarker.SEVERITY_ERROR); return false; } @@ -282,7 +284,7 @@ public class AidlProcessor extends SourceProcessor { } // mark the file - BaseProjectHelper.markResource(file, AndroidConstants.MARKER_AIDL, msg, line, + BaseProjectHelper.markResource(file, AdtConstants.MARKER_AIDL, msg, line, IMarker.SEVERITY_ERROR); // success, go to the next line @@ -329,7 +331,7 @@ public class AidlProcessor extends SourceProcessor { // Build the Java file name from the aidl name. String javaName = sourceFile.getName().replaceAll( - AndroidConstants.RE_AIDL_EXT, AndroidConstants.DOT_JAVA); + AdtConstants.RE_AIDL_EXT, AdtConstants.DOT_JAVA); // get the resource for the java file. IFile javaFile = destinationFolder.getFile(javaName); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java index 545d0f9..1d6bad3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.build; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AndroidPrintStream; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; @@ -145,10 +145,10 @@ public class BuildHelper { // need to figure out some path before we can execute aapt; // get the resource folder - IFolder resFolder = mProject.getFolder(AndroidConstants.WS_RESOURCES); + IFolder resFolder = mProject.getFolder(AdtConstants.WS_RESOURCES); // and the assets folder - IFolder assetsFolder = mProject.getFolder(AndroidConstants.WS_ASSETS); + IFolder assetsFolder = mProject.getFolder(AdtConstants.WS_ASSETS); // we need to make sure this one exists. if (assetsFolder.exists() == false) { @@ -747,7 +747,7 @@ public class BuildHelper { // (This is to handle the case of reference Android projects in the context of // instrumentation projects that need to reference the projects to be tested). if (referencedJavaProject.getProject().hasNature( - AndroidConstants.NATURE_DEFAULT) == false) { + AdtConstants.NATURE_DEFAULT) == false) { writeStandardProjectResources(apkBuilder, referencedJavaProject, wsRoot, list); } } @@ -842,7 +842,7 @@ public class BuildHelper { IResource resource = wsRoot.findMember(path); // case of a jar file (which could be relative to the workspace or a full path) - if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { + if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { if (resource != null && resource.exists() && resource.getType() == IResource.FILE) { oslibraryList.add(resource.getLocation().toOSString()); @@ -908,7 +908,7 @@ public class BuildHelper { // only include output from non android referenced project // (This is to handle the case of reference Android projects in the context of // instrumentation projects that need to reference the projects to be tested). - if (javaProject.getProject().hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (javaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT) == false) { // get the output folder IPath path = null; try { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java index 30e3d5c..f13e201 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java @@ -16,14 +16,15 @@ package com.android.ide.eclipse.adt.internal.build; +import com.android.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.resources.ResourceFolderType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; @@ -70,7 +71,7 @@ public class RenderScriptProcessor extends SourceProcessor { boolean r = super.handleGeneratedFile(file, kind); if (r == false && kind == IResourceDelta.REMOVED && - AndroidConstants.EXT_DEP.equalsIgnoreCase(file.getFileExtension())) { + AdtConstants.EXT_DEP.equalsIgnoreCase(file.getFileExtension())) { // This looks to be an extension file. // For futureproofness let's make sure this dependency file was generated by // this processor even if it's the only processor using them for now. @@ -85,8 +86,8 @@ public class RenderScriptProcessor extends SourceProcessor { // remove the file name segment relative = relative.removeLastSegments(1); // add the file name of a Renderscript file. - relative = relative.append(file.getName().replaceAll(AndroidConstants.RE_DEP_EXT, - AndroidConstants.DOT_RS)); + relative = relative.append(file.getName().replaceAll(AdtConstants.RE_DEP_EXT, + AdtConstants.DOT_RS)); // now look for a match in the source folders. List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths( @@ -122,7 +123,7 @@ public class RenderScriptProcessor extends SourceProcessor { @Override protected String getExtension() { - return AndroidConstants.EXT_RS; + return AdtConstants.EXT_RS; } @Override @@ -140,7 +141,7 @@ public class RenderScriptProcessor extends SourceProcessor { IFolder genFolder = getGenFolder(); IFolder rawFolder = project.getFolder( - new Path(SdkConstants.FD_RES).append(SdkConstants.FD_RAW)); + new Path(SdkConstants.FD_RES).append(AndroidConstants.FD_RES_RAW)); int depIndex; @@ -166,7 +167,7 @@ public class RenderScriptProcessor extends SourceProcessor { boolean someSuccess = false; // remove the generic marker from the project - builder.removeMarkersFromResource(project, AndroidConstants.MARKER_RENDERSCRIPT); + builder.removeMarkersFromResource(project, AdtConstants.MARKER_RENDERSCRIPT); // loop until we've compile them all for (IFile sourceFile : sources) { @@ -182,11 +183,11 @@ public class RenderScriptProcessor extends SourceProcessor { } // Remove the RS error markers from the source file and the dependencies - builder.removeMarkersFromResource(sourceFile, AndroidConstants.MARKER_RENDERSCRIPT); + builder.removeMarkersFromResource(sourceFile, AdtConstants.MARKER_RENDERSCRIPT); SourceFileData data = getFileData(sourceFile); if (data != null) { for (IFile dep : data.getDependencyFiles()) { - builder.removeMarkersFromResource(dep, AndroidConstants.MARKER_RENDERSCRIPT); + builder.removeMarkersFromResource(dep, AdtConstants.MARKER_RENDERSCRIPT); } } @@ -254,7 +255,7 @@ public class RenderScriptProcessor extends SourceProcessor { // mark the project BaseProjectHelper.markResource(project, - AndroidConstants.MARKER_RENDERSCRIPT, + AdtConstants.MARKER_RENDERSCRIPT, "Unparsed Renderscript error! Check the console for output.", IMarker.SEVERITY_ERROR); } else { @@ -268,7 +269,7 @@ public class RenderScriptProcessor extends SourceProcessor { String msg = String.format( "Error executing Renderscript. Please check llvm-rs-cc is present at %1$s", command[0]); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_RENDERSCRIPT, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_RENDERSCRIPT, msg, IMarker.SEVERITY_ERROR); return false; } catch (InterruptedException e) { @@ -276,7 +277,7 @@ public class RenderScriptProcessor extends SourceProcessor { String msg = String.format( "Error executing Renderscript. Please check llvm-rs-cc is present at %1$s", command[0]); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_RENDERSCRIPT, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_RENDERSCRIPT, msg, IMarker.SEVERITY_ERROR); return false; } @@ -344,7 +345,7 @@ public class RenderScriptProcessor extends SourceProcessor { } // mark the file - BaseProjectHelper.markResource(f, AndroidConstants.MARKER_RENDERSCRIPT, msg, line, + BaseProjectHelper.markResource(f, AdtConstants.MARKER_RENDERSCRIPT, msg, line, IMarker.SEVERITY_ERROR); // success, go to the next line @@ -417,8 +418,8 @@ public class RenderScriptProcessor extends SourceProcessor { private IFile getDependencyFileFor(IFile sourceFile) { IFolder depFolder = getDependencyFolder(sourceFile); - return depFolder.getFile(sourceFile.getName().replaceAll(AndroidConstants.RE_RS_EXT, - AndroidConstants.DOT_DEP)); + return depFolder.getFile(sourceFile.getName().replaceAll(AdtConstants.RE_RS_EXT, + AdtConstants.DOT_DEP)); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java index af4d0ab..293b340 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java @@ -18,7 +18,7 @@ package com.android.ide.eclipse.adt.internal.build.builders; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.BuildHelper; import com.android.ide.eclipse.adt.internal.build.Messages; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; @@ -151,7 +151,7 @@ public abstract class BaseBuilder extends IncrementalProjectBuilder { IFile file = (IFile)resource; // remove previous markers - removeMarkersFromResource(file, AndroidConstants.MARKER_XML); + removeMarkersFromResource(file, AdtConstants.MARKER_XML); // create the error handler XmlErrorHandler reporter = new XmlErrorHandler(file, visitor); @@ -309,9 +309,9 @@ public abstract class BaseBuilder extends IncrementalProjectBuilder { } // abort if there are TARGET or ADT type markers - stopOnMarker(iProject, AndroidConstants.MARKER_TARGET, IResource.DEPTH_ZERO, + stopOnMarker(iProject, AdtConstants.MARKER_TARGET, IResource.DEPTH_ZERO, false /*checkSeverity*/); - stopOnMarker(iProject, AndroidConstants.MARKER_ADT, IResource.DEPTH_ZERO, + stopOnMarker(iProject, AdtConstants.MARKER_ADT, IResource.DEPTH_ZERO, false /*checkSeverity*/); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java index 1d3d13b..71c38f8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.build.builders; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AndroidPrintStream; import com.android.ide.eclipse.adt.internal.build.AaptExecException; import com.android.ide.eclipse.adt.internal.build.AaptParser; @@ -134,7 +134,7 @@ public class PostCompilerBuilder extends BaseBuilder { int type = resource.getType(); if (type == IResource.FILE) { String ext = resource.getFileExtension(); - if (AndroidConstants.EXT_CLASS.equals(ext)) { + if (AdtConstants.EXT_CLASS.equals(ext)) { mConvertToDex = true; } } @@ -178,7 +178,7 @@ public class PostCompilerBuilder extends BaseBuilder { private ResourceMarker mResourceMarker = new ResourceMarker() { public void setWarning(IResource resource, String message) { - BaseProjectHelper.markResource(resource, AndroidConstants.MARKER_PACKAGING, + BaseProjectHelper.markResource(resource, AdtConstants.MARKER_PACKAGING, message, IMarker.SEVERITY_WARNING); } }; @@ -196,8 +196,8 @@ public class PostCompilerBuilder extends BaseBuilder { IProject project = getProject(); // Clear the project of the generic markers - removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE); - removeMarkersFromContainer(project, AndroidConstants.MARKER_PACKAGING); + removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); + removeMarkersFromContainer(project, AdtConstants.MARKER_PACKAGING); } // build() returns a list of project from which this project depends for future compilation. @@ -332,11 +332,11 @@ public class PostCompilerBuilder extends BaseBuilder { } // remove older packaging markers. - removeMarkersFromContainer(javaProject.getProject(), AndroidConstants.MARKER_PACKAGING); + removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING); if (outputFolder == null) { // mark project and exit - markProject(AndroidConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output, + markProject(AdtConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output, IMarker.SEVERITY_ERROR); return allRefProjects; } @@ -357,7 +357,7 @@ public class PostCompilerBuilder extends BaseBuilder { if (mPackageResources == false) { // check the full resource package - tmp = outputFolder.findMember(AndroidConstants.FN_RESOURCES_AP_); + tmp = outputFolder.findMember(AdtConstants.FN_RESOURCES_AP_); if (tmp == null || tmp.exists() == false) { mPackageResources = true; mBuildFinalPackage = true; @@ -419,13 +419,13 @@ public class PostCompilerBuilder extends BaseBuilder { // mark project and exit String msg = String.format(Messages.s_File_Missing, SdkConstants.FN_ANDROID_MANIFEST_XML); - markProject(AndroidConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); return allRefProjects; } IPath binLocation = outputFolder.getLocation(); if (binLocation == null) { - markProject(AndroidConstants.MARKER_PACKAGING, Messages.Output_Missing, + markProject(AdtConstants.MARKER_PACKAGING, Messages.Output_Missing, IMarker.SEVERITY_ERROR); return allRefProjects; } @@ -445,14 +445,14 @@ public class PostCompilerBuilder extends BaseBuilder { // first we check if we need to package the resources. if (mPackageResources) { // remove some aapt_package only markers. - removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE); + removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_PACKAGE); try { helper.packageResources(manifestFile, libProjects, null /*resfilter*/, 0 /*versionCode */, osBinPath, - AndroidConstants.FN_RESOURCES_AP_); + AdtConstants.FN_RESOURCES_AP_); } catch (AaptExecException e) { - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, e.getMessage(), IMarker.SEVERITY_ERROR); return allRefProjects; } catch (AaptResultException e) { @@ -468,7 +468,7 @@ public class PostCompilerBuilder extends BaseBuilder { // therefore not all files that should have been marked, were marked), // we put a generic marker on the project and abort. BaseProjectHelper.markResource(project, - AndroidConstants.MARKER_PACKAGING, + AdtConstants.MARKER_PACKAGING, Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR); } @@ -493,7 +493,7 @@ public class PostCompilerBuilder extends BaseBuilder { String message = e.getMessage(); AdtPlugin.printErrorToConsole(project, message); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, message, IMarker.SEVERITY_ERROR); Throwable cause = e.getCause(); @@ -523,7 +523,7 @@ public class PostCompilerBuilder extends BaseBuilder { SdkConstants.FN_APK_CLASSES_DEX; try { helper.finalDebugPackage( - osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_, + osBinPath + File.separator + AdtConstants.FN_RESOURCES_AP_, classesDexPath, osFinalPackagePath, javaProject, libProjects, referencedJavaProjects, mResourceMarker); } catch (KeytoolException e) { @@ -531,7 +531,7 @@ public class PostCompilerBuilder extends BaseBuilder { // mark the project with the standard message String msg = String.format(Messages.Final_Archive_Error_s, eMessage); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); // output more info in the console @@ -547,19 +547,19 @@ public class PostCompilerBuilder extends BaseBuilder { // mark the project with the standard message String msg = String.format(Messages.Final_Archive_Error_s, eMessage); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); } catch (AndroidLocationException e) { String eMessage = e.getMessage(); // mark the project with the standard message String msg = String.format(Messages.Final_Archive_Error_s, eMessage); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); } catch (NativeLibInJarException e) { String msg = e.getMessage(); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); AdtPlugin.printErrorToConsole(project, (Object[]) e.getAdditionalInfo()); @@ -567,7 +567,7 @@ public class PostCompilerBuilder extends BaseBuilder { // mark project and return String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); } catch (DuplicateFileException e) { String msg1 = String.format( @@ -575,7 +575,7 @@ public class PostCompilerBuilder extends BaseBuilder { e.getArchivePath(), e.getFile1(), e.getFile2()); String msg2 = String.format(Messages.Final_Archive_Error_s, msg1); AdtPlugin.printErrorToConsole(project, msg2); - BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg2, + BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg2, IMarker.SEVERITY_ERROR); } @@ -618,7 +618,7 @@ public class PostCompilerBuilder extends BaseBuilder { msg = String.format("Unknown error: %1$s", msg); AdtPlugin.logAndPrintError(exception, project.getName(), msg); - markProject(AndroidConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_PACKAGING, msg, IMarker.SEVERITY_ERROR); } return allRefProjects; @@ -643,13 +643,13 @@ public class PostCompilerBuilder extends BaseBuilder { // do a (hopefully quick) search for Precompiler type markers. Those are always only // errors. - stopOnMarker(iProject, AndroidConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE, + stopOnMarker(iProject, AdtConstants.MARKER_AAPT_COMPILE, IResource.DEPTH_INFINITE, false /*checkSeverity*/); - stopOnMarker(iProject, AndroidConstants.MARKER_AIDL, IResource.DEPTH_INFINITE, + stopOnMarker(iProject, AdtConstants.MARKER_AIDL, IResource.DEPTH_INFINITE, false /*checkSeverity*/); - stopOnMarker(iProject, AndroidConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE, + stopOnMarker(iProject, AdtConstants.MARKER_RENDERSCRIPT, IResource.DEPTH_INFINITE, false /*checkSeverity*/); - stopOnMarker(iProject, AndroidConstants.MARKER_ANDROID, IResource.DEPTH_ZERO, + stopOnMarker(iProject, AdtConstants.MARKER_ANDROID, IResource.DEPTH_ZERO, false /*checkSeverity*/); // do a search for JDT markers. Those can be errors or warnings diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java index 7df8ef4..7e426fd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerDeltaVisitor.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.build.builders; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.BuildHelper; import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder.BaseDeltaVisitor; import com.android.sdklib.SdkConstants; @@ -177,7 +177,7 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor // just check this is a .class file. Any modification will // trigger a change in the classes.dex file String ext = resource.getFileExtension(); - if (AndroidConstants.EXT_CLASS.equalsIgnoreCase(ext)) { + if (AdtConstants.EXT_CLASS.equalsIgnoreCase(ext)) { mConvertToDex = true; mMakeFinalPackage = true; @@ -198,8 +198,8 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor mConvertToDex = true; mMakeFinalPackage = true; } else if (resourceName.equalsIgnoreCase( - AndroidConstants.FN_RESOURCES_AP_) || - AndroidConstants.PATTERN_RESOURCES_S_AP_.matcher( + AdtConstants.FN_RESOURCES_AP_) || + AdtConstants.PATTERN_RESOURCES_S_AP_.matcher( resourceName).matches()) { // or if the default resources.ap_ or a configured version // (resources-###.ap_) was removed. @@ -237,7 +237,7 @@ public class PostCompilerDeltaVisitor extends BaseDeltaVisitor } else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) { // inside the native library folder. Test if the changed resource is a .so file. if (type == IResource.FILE && - (AndroidConstants.EXT_NATIVE_LIB.equalsIgnoreCase(path.getFileExtension()) + (AdtConstants.EXT_NATIVE_LIB.equalsIgnoreCase(path.getFileExtension()) || SdkConstants.FN_GDBSERVER.equals(resource.getName()))) { mMakeFinalPackage = true; return false; // return false for file. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java index fd3a07d..943dcfe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java @@ -16,13 +16,13 @@ package com.android.ide.eclipse.adt.internal.build.builders; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.build.AaptParser; import com.android.ide.eclipse.adt.internal.build.AidlProcessor; -import com.android.ide.eclipse.adt.internal.build.SourceProcessor; import com.android.ide.eclipse.adt.internal.build.Messages; import com.android.ide.eclipse.adt.internal.build.RenderScriptProcessor; +import com.android.ide.eclipse.adt.internal.build.SourceProcessor; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; @@ -303,7 +303,7 @@ public class PreCompilerBuilder extends BaseBuilder { String msg = String.format(Messages.s_File_Missing, SdkConstants.FN_ANDROID_MANIFEST_XML); AdtPlugin.printErrorToConsole(project, msg); - markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; @@ -350,7 +350,7 @@ public class PreCompilerBuilder extends BaseBuilder { AndroidVersion projectVersion = projectTarget.getVersion(); // remove earlier marker from the manifest - removeMarkersFromResource(manifestFile, AndroidConstants.MARKER_ADT); + removeMarkersFromResource(manifestFile, AdtConstants.MARKER_ADT); if (minSdkValue != -1) { String codename = projectVersion.getCodename(); @@ -360,7 +360,7 @@ public class PreCompilerBuilder extends BaseBuilder { "Platform %1$s is a preview and requires application manifest to set %2$s to '%1$s'", codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; } else if (minSdkValue < projectVersion.getApiLevel()) { @@ -370,7 +370,7 @@ public class PreCompilerBuilder extends BaseBuilder { AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkValue, projectVersion.getApiLevel()); AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_WARNING); } else if (minSdkValue > projectVersion.getApiLevel()) { // integer minSdk is too high for the target => warning @@ -379,7 +379,7 @@ public class PreCompilerBuilder extends BaseBuilder { AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkValue, projectVersion.getApiLevel()); AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_WARNING); } } else { @@ -392,7 +392,7 @@ public class PreCompilerBuilder extends BaseBuilder { "Manifest attribute '%1$s' is set to '%2$s'. Integer is expected.", AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, minSdkVersion); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; } else if (codename.equals(minSdkVersion) == false) { @@ -401,7 +401,7 @@ public class PreCompilerBuilder extends BaseBuilder { "Value of manifest attribute '%1$s' does not match platform codename '%2$s'", AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION, codename); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; } @@ -414,7 +414,7 @@ public class PreCompilerBuilder extends BaseBuilder { "Platform %1$s is a preview and requires application manifests to set %2$s to '%1$s'", codename, AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, msg, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; } @@ -424,7 +424,7 @@ public class PreCompilerBuilder extends BaseBuilder { String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, SdkConstants.FN_ANDROID_MANIFEST_XML); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; @@ -434,7 +434,7 @@ public class PreCompilerBuilder extends BaseBuilder { "Application package '%1$s' must have a minimum of 2 segments.", SdkConstants.FN_ANDROID_MANIFEST_XML); AdtPlugin.printErrorToConsole(project, msg); - BaseProjectHelper.markResource(manifestFile, AndroidConstants.MARKER_ADT, + BaseProjectHelper.markResource(manifestFile, AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); return result; @@ -526,41 +526,49 @@ public class PreCompilerBuilder extends BaseBuilder { // remove all the derived resources from the 'gen' source folder. if (mGenFolder != null) { + // gen folder should not be derived, but previous version could set it to derived + // so we make sure this isn't the case (or it'll get deleted by the clean) + mGenFolder.setDerived(false); + removeDerivedResources(mGenFolder, monitor); } // Clear the project of the generic markers - removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_COMPILE); - removeMarkersFromContainer(project, AndroidConstants.MARKER_XML); - removeMarkersFromContainer(project, AndroidConstants.MARKER_AIDL); - removeMarkersFromContainer(project, AndroidConstants.MARKER_RENDERSCRIPT); - removeMarkersFromContainer(project, AndroidConstants.MARKER_ANDROID); + removeMarkersFromContainer(project, AdtConstants.MARKER_AAPT_COMPILE); + removeMarkersFromContainer(project, AdtConstants.MARKER_XML); + removeMarkersFromContainer(project, AdtConstants.MARKER_AIDL); + removeMarkersFromContainer(project, AdtConstants.MARKER_RENDERSCRIPT); + removeMarkersFromContainer(project, AdtConstants.MARKER_ANDROID); } @Override protected void startupOnInitialize() { - super.startupOnInitialize(); - - IProject project = getProject(); + try { + super.startupOnInitialize(); - // load the previous IFolder and java package. - mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE); + IProject project = getProject(); - // get the source folder in which all the Java files are created - mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); + // load the previous IFolder and java package. + mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE); - // Load the current compile flags. We ask for true if not found to force a recompile. - mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); + // get the source folder in which all the Java files are created + mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); + mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder); - IJavaProject javaProject = JavaCore.create(project); + // Load the current compile flags. We ask for true if not found to force a recompile. + mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); - // load the source processors - SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder); - mProcessors.add(aidlProcessor); - SourceProcessor renderScriptProcessor = new RenderScriptProcessor(javaProject, mGenFolder); - mProcessors.add(renderScriptProcessor); + IJavaProject javaProject = JavaCore.create(project); - mDerivedProgressMonitor = new DerivedProgressMonitor(mGenFolder); + // load the source processors + SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder); + mProcessors.add(aidlProcessor); + SourceProcessor renderScriptProcessor = new RenderScriptProcessor(javaProject, + mGenFolder); + mProcessors.add(renderScriptProcessor); + } catch (Throwable throwable) { + AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()"); + } } /** @@ -576,7 +584,7 @@ public class PreCompilerBuilder extends BaseBuilder { private void handleResources(IProject project, String javaPackage, IAndroidTarget projectTarget, IFile manifest, List<IProject> libProjects) throws CoreException, AbortBuildException { // get the resource folder - IFolder resFolder = project.getFolder(AndroidConstants.WS_RESOURCES); + IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES); // get the file system path IPath outputLocation = mGenFolder.getLocation(); @@ -591,8 +599,8 @@ public class PreCompilerBuilder extends BaseBuilder { String osManifestPath = manifestLocation.toOSString(); // remove the aapt markers - removeMarkersFromResource(manifest, AndroidConstants.MARKER_AAPT_COMPILE); - removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE); + removeMarkersFromResource(manifest, AdtConstants.MARKER_AAPT_COMPILE); + removeMarkersFromContainer(resFolder, AdtConstants.MARKER_AAPT_COMPILE); AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Preparing_Generated_Files); @@ -661,7 +669,7 @@ public class PreCompilerBuilder extends BaseBuilder { // We actually need to delete the manifest.java as it may become empty and // in this case aapt doesn't generate an empty one, but instead doesn't // touch it. - IFile manifestJavaFile = packageFolder.getFile(AndroidConstants.FN_MANIFEST_CLASS); + IFile manifestJavaFile = packageFolder.getFile(AdtConstants.FN_MANIFEST_CLASS); manifestJavaFile.getLocation().toFile().delete(); // launch aapt: create the command line @@ -737,7 +745,7 @@ public class PreCompilerBuilder extends BaseBuilder { // (and therefore not all files that should have been marked, // were marked), we put a generic marker on the project and abort. if (parsingError) { - markProject(AndroidConstants.MARKER_ADT, + markProject(AdtConstants.MARKER_ADT, Messages.Unparsed_AAPT_Errors, IMarker.SEVERITY_ERROR); } @@ -751,7 +759,7 @@ public class PreCompilerBuilder extends BaseBuilder { // something happen while executing the process, // mark the project and exit String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); - markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); // This interrupts the build. throw new AbortBuildException(); @@ -759,7 +767,7 @@ public class PreCompilerBuilder extends BaseBuilder { // we got interrupted waiting for the process to end... // mark the project and exit String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); - markProject(AndroidConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); // This interrupts the build. throw new AbortBuildException(); @@ -783,11 +791,11 @@ public class PreCompilerBuilder extends BaseBuilder { */ private IPath getJavaPackagePath(String javaPackageName) { // convert the java package into path - String[] segments = javaPackageName.split(AndroidConstants.RE_DOT); + String[] segments = javaPackageName.split(AdtConstants.RE_DOT); StringBuilder path = new StringBuilder(); for (String s : segments) { - path.append(AndroidConstants.WS_SEP_CHAR); + path.append(AdtConstants.WS_SEP_CHAR); path.append(s); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java index 6660cd2..cd99fbe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerDeltaVisitor.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.build.builders; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.SourceChangeHandler; import com.android.ide.eclipse.adt.internal.build.SourceProcessor; import com.android.ide.eclipse.adt.internal.build.Messages; @@ -199,9 +199,9 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta IFile manifestFile = (IFile)resource; if (manifestFile.exists()) { - manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true, + manifestFile.deleteMarkers(AdtConstants.MARKER_XML, true, IResource.DEPTH_ZERO); - manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true, + manifestFile.deleteMarkers(AdtConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO); } @@ -262,8 +262,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta String fileName = resource.getName(); // Special case of R.java/Manifest.java. - if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) || - AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) { + if (AdtConstants.FN_RESOURCE_CLASS.equals(fileName) || + AdtConstants.FN_MANIFEST_CLASS.equals(fileName)) { // if it was removed, there's a possibility that it was removed due to a // package change, or an aidl that was removed, but the only thing // that will happen is that we'll have an extra build. Not much of a problem. @@ -324,17 +324,17 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta case IResourceDelta.CHANGED: // display verbose message message = String.format(Messages.s_Modified_Recreating_s, p, - AndroidConstants.FN_RESOURCE_CLASS); + AdtConstants.FN_RESOURCE_CLASS); break; case IResourceDelta.ADDED: // display verbose message message = String.format(Messages.Added_s_s_Needs_Updating, p, - AndroidConstants.FN_RESOURCE_CLASS); + AdtConstants.FN_RESOURCE_CLASS); break; case IResourceDelta.REMOVED: // display verbose message message = String.format(Messages.s_Removed_s_Needs_Updating, p, - AndroidConstants.FN_RESOURCE_CLASS); + AdtConstants.FN_RESOURCE_CLASS); break; } if (message != null) { @@ -346,7 +346,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDelta handler.handleResourceFile((IFile)resource, kind); } - if (AndroidConstants.EXT_XML.equalsIgnoreCase(ext)) { + if (AdtConstants.EXT_XML.equalsIgnoreCase(ext)) { if (kind != IResourceDelta.REMOVED) { // check xml Validity mBuilder.checkXML(resource, this); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java index 0908260..950200a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.build.builders; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.build.Messages; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; @@ -64,7 +64,7 @@ public class ResourceManagerBuilder extends BaseBuilder { IProject project = getProject(); // Clear the project of the generic markers - removeMarkersFromContainer(project, AndroidConstants.MARKER_ADT); + removeMarkersFromContainer(project, AdtConstants.MARKER_ADT); } // build() returns a list of project from which this project depends for future compilation. @@ -77,7 +77,7 @@ public class ResourceManagerBuilder extends BaseBuilder { IJavaProject javaProject = JavaCore.create(project); // Clear the project of the generic markers - removeMarkersFromContainer(project, AndroidConstants.MARKER_ADT); + removeMarkersFromContainer(project, AdtConstants.MARKER_ADT); // check for existing target marker, in which case we abort. // (this means: no SDK, no target, or unresolvable target.) @@ -104,7 +104,7 @@ public class ResourceManagerBuilder extends BaseBuilder { } if (errorMessage != null) { - markProject(AndroidConstants.MARKER_ADT, errorMessage, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_ADT, errorMessage, IMarker.SEVERITY_ERROR); AdtPlugin.printErrorToConsole(project, errorMessage); return null; @@ -115,7 +115,7 @@ public class ResourceManagerBuilder extends BaseBuilder { if (osSdkFolder == null || osSdkFolder.length() == 0) { AdtPlugin.printErrorToConsole(project, Messages.No_SDK_Setup_Error); - markProject(AndroidConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, + markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, IMarker.SEVERITY_ERROR); return null; @@ -166,7 +166,7 @@ public class ResourceManagerBuilder extends BaseBuilder { } AdtPlugin.printErrorToConsole(project, message); - markProject(AndroidConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); + markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); return null; } else if (hasGenSrcFolder == false || genFolderPresent == false) { @@ -191,7 +191,6 @@ public class ResourceManagerBuilder extends BaseBuilder { "Creating 'gen' source folder for generated Java files"); genFolder.create(true /* force */, true /* local */, new SubProgressMonitor(monitor, 10)); - genFolder.setDerived(true); } // add it to the source folder list, if needed only (or it will throw) @@ -214,12 +213,12 @@ public class ResourceManagerBuilder extends BaseBuilder { AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, project, Messages.Refreshing_Res); // refresh the res folder. - IFolder resFolder = project.getFolder(AndroidConstants.WS_RESOURCES); + IFolder resFolder = project.getFolder(AdtConstants.WS_RESOURCES); resFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); // Also refresh the assets folder to make sure the ApkBuilder // will now it's changed and will force a new resource packaging. - IFolder assetsFolder = project.getFolder(AndroidConstants.WS_ASSETS); + IFolder assetsFolder = project.getFolder(AdtConstants.WS_ASSETS); assetsFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java index 4c542b1..17ffe30 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java @@ -16,6 +16,12 @@ package com.android.ide.eclipse.adt.internal.editors; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME; + +import com.android.ide.common.api.IAttributeInfo; +import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; @@ -25,15 +31,19 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttribu import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.TextValueDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiFlagAttributeNode; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiResourceAttributeNode; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.sdklib.SdkConstants; +import com.android.util.Pair; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.text.contentassist.CompletionProposal; @@ -44,21 +54,32 @@ import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.graphics.Image; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PlatformUI; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.regex.Pattern; /** * Content Assist Processor for Android XML files + * <p> + * Remaining corner cases: + * <ul> + * <li>Completion does not work right if there is a space between the = and the opening + * quote. + * <li>Replacement completion does not work right if the caret is to the left of the + * opening quote, where the opening quote is a single quote, and the replacement items use + * double quotes. + * </ul> */ +@SuppressWarnings("restriction") // XML model public abstract class AndroidContentAssist implements IContentAssistProcessor { /** Regexp to detect a full attribute after an element tag. @@ -72,7 +93,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { "^ *[a-zA-Z_:]+ *= *(?:\"[^<\"]*\"|'[^<']*')"); //$NON-NLS-1$ /** Regexp to detect an element tag name */ - private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$ + private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:-]+"); //$NON-NLS-1$ /** Regexp to detect whitespace */ private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ @@ -86,7 +107,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { private final int mDescriptorId; - private AndroidXmlEditor mEditor; + protected AndroidXmlEditor mEditor; /** * Constructor for AndroidContentAssist @@ -113,9 +134,10 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) */ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { + String wordPrefix = extractElementPrefix(viewer, offset); if (mEditor == null) { - mEditor = getAndroidXmlEditor(viewer); + mEditor = AndroidXmlEditor.getAndroidXmlEditor(viewer); if (mEditor == null) { // This should not happen. Duck and forget. AdtPlugin.log(IStatus.ERROR, "Editor not found during completion"); @@ -123,83 +145,147 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } } - UiElementNode rootUiNode = mEditor.getUiRootNode(); - - Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor - or String or null */ - String parent = ""; //$NON-NLS-1$ - String wordPrefix = extractElementPrefix(viewer, offset); - char needTag = 0; - boolean isElement = false; - boolean isAttribute = false; - - Node currentNode = getNode(viewer, offset); - if (currentNode == null) - return null; - - // check to see if we can find a UiElementNode matching this XML node - UiElementNode currentUiNode = - rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode); + // List of proposals, in the order presented to the user. + List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(80); - if (currentNode == null) { - // Should not happen (an XML doc always has at least a doc node). Just give up. + // Look up the caret context - where in an element, or between elements, or + // within an element's children, is the given caret offset located? + Pair<Node, Node> context = DomUtilities.getNodeContext(viewer.getDocument(), offset); + if (context == null) { return null; } + Node parentNode = context.getFirst(); + Node currentNode = context.getSecond(); + assert parentNode != null || currentNode != null; - if (currentNode.getNodeType() == Node.ELEMENT_NODE) { - parent = currentNode.getNodeName(); - - if (wordPrefix.equals(parent)) { - // We are still editing the element's tag name, not the attributes - // (the element's tag name may not even be complete) - isElement = true; - choices = getChoicesForElement(parent, currentNode); + UiElementNode rootUiNode = mEditor.getUiRootNode(); + if (currentNode == null || currentNode.getNodeType() == Node.TEXT_NODE) { + UiElementNode parentUiNode = + rootUiNode == null ? null : rootUiNode.findXmlNode(parentNode); + computeTextValues(proposals, offset, parentNode, currentNode, parentUiNode, + wordPrefix); + } else if (currentNode.getNodeType() == Node.ELEMENT_NODE) { + String parent = currentNode.getNodeName(); + AttribInfo info = parseAttributeInfo(viewer, offset, offset - wordPrefix.length()); + char nextChar = extractChar(viewer, offset); + if (info != null) { + // check to see if we can find a UiElementNode matching this XML node + UiElementNode currentUiNode = rootUiNode == null + ? null : rootUiNode.findXmlNode(currentNode); + computeAttributeProposals(proposals, viewer, offset, wordPrefix, currentUiNode, + parentNode, currentNode, parent, info, nextChar); } else { - // We're not editing the current node name, so we might be editing its - // attributes instead... - isAttribute = true; - AttribInfo info = parseAttributeInfo(viewer, offset); - if (info != null) { - // We're editing attributes in an element node (either the attributes' names - // or their values). - choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info); - - if (info.correctedPrefix != null) { - wordPrefix = info.correctedPrefix; - } - needTag = info.needTag; - } + computeNonAttributeProposals(viewer, offset, wordPrefix, proposals, parentNode, + currentNode, parent, nextChar); } - } else if (currentNode.getNodeType() == Node.TEXT_NODE) { - isElement = true; - // Examine the parent of the text node. - choices = getChoicesForTextNode(currentNode); } - // Abort if we can't recognize the context or there are no completion choices - if (choices == null || choices.length == 0) return null; + return proposals.toArray(new ICompletionProposal[proposals.size()]); + } + + private void computeNonAttributeProposals(ITextViewer viewer, int offset, String wordPrefix, + List<ICompletionProposal> proposals, Node parentNode, Node currentNode, String parent, + char nextChar) { + if (startsWith(parent, wordPrefix)) { + // We are still editing the element's tag name, not the attributes + // (the element's tag name may not even be complete) + + Object[] choices = getChoicesForElement(parent, currentNode); + if (choices == null || choices.length == 0) { + return; + } + + int selectionLength = getSelectionLength(viewer); + int replaceLength = parent.length() - wordPrefix.length(); + boolean isNew = replaceLength == 0 && nextNonspaceChar(viewer, offset) == '<'; + // Special case: if we are right before the beginning of a new + // element, wipe out the replace length such that we insert before it, + // we don't edit the current element. + if (wordPrefix.length() == 0 && nextChar == '<') { + replaceLength = 0; + isNew = true; + } - if (isElement) { // If we found some suggestions, do we need to add an opening "<" bracket // for the element? We don't if the cursor is right after "<" or "</". // Per XML Spec, there's no whitespace between "<" or "</" and the tag name. - int offset2 = offset - wordPrefix.length() - 1; - int c1 = extractChar(viewer, offset2); - if (!((c1 == '<') || (c1 == '/' && extractChar(viewer, offset2 - 1) == '<'))) { - needTag = '<'; - } + char needTag = computeElementNeedTag(viewer, offset, wordPrefix); + + addMatchingProposals(proposals, choices, offset, + parentNode != null ? parentNode : null, wordPrefix, needTag, + false /* isAttribute */, isNew, false /*isComplete*/, + selectionLength + replaceLength); } + } + private int getSelectionLength(ITextViewer viewer) { // get the selection length int selectionLength = 0; ISelection selection = viewer.getSelectionProvider().getSelection(); if (selection instanceof TextSelection) { - TextSelection textSelection = (TextSelection)selection; + TextSelection textSelection = (TextSelection) selection; selectionLength = textSelection.getLength(); } + return selectionLength; + } + + private void computeAttributeProposals(List<ICompletionProposal> proposals, ITextViewer viewer, + int offset, String wordPrefix, UiElementNode currentUiNode, Node parentNode, + Node currentNode, String parent, AttribInfo info, char nextChar) { + // We're editing attributes in an element node (either the attributes' names + // or their values). + + if (info.isInValue) { + computeAttributeValues(proposals, offset, parent, info.name, currentNode, + wordPrefix, info.skipEndTag, info.replaceLength); + } + + // Look up attribute proposals based on descriptors + Object[] choices = getChoicesForAttribute(parent, currentNode, currentUiNode, + info, wordPrefix); + if (choices == null || choices.length == 0) { + return; + } - return computeProposals(offset, currentNode, choices, wordPrefix, needTag, - isAttribute, selectionLength); + int selectionLength = getSelectionLength(viewer); + int replaceLength = info.replaceLength; + if (info.correctedPrefix != null) { + wordPrefix = info.correctedPrefix; + } + char needTag = info.needTag; + // Look to the right and see if we're followed by whitespace + boolean isNew = replaceLength == 0 + && (Character.isWhitespace(nextChar) || nextChar == '>' || nextChar == '/'); + + addMatchingProposals(proposals, choices, offset, parentNode != null ? parentNode : null, + wordPrefix, needTag, true /* isAttribute */, isNew, info.skipEndTag, + selectionLength + replaceLength); + } + + private char computeElementNeedTag(ITextViewer viewer, int offset, String wordPrefix) { + char needTag = 0; + int offset2 = offset - wordPrefix.length() - 1; + char c1 = extractChar(viewer, offset2); + if (!((c1 == '<') || (c1 == '/' && extractChar(viewer, offset2 - 1) == '<'))) { + needTag = '<'; + } + return needTag; + } + + protected int computeTextReplaceLength(Node currentNode, int offset) { + if (currentNode == null) { + return 0; + } + + assert currentNode != null && currentNode.getNodeType() == Node.TEXT_NODE; + + String nodeValue = currentNode.getNodeValue(); + int relativeOffset = offset - ((IndexedRegion) currentNode).getStartOffset(); + int lineEnd = nodeValue.indexOf('\n', relativeOffset); + if (lineEnd == -1) { + lineEnd = nodeValue.length(); + } + return lineEnd - relativeOffset; } /** @@ -211,7 +297,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * e.g. {@link SdkConstants#NS_RESOURCES} * @return The first prefix declared or the default "android" prefix. */ - private String lookupNamespacePrefix(Node node, String nsUri) { + private static String lookupNamespacePrefix(Node node, String nsUri) { // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java // The following emulates this: // String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES); @@ -273,7 +359,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (grandparent != null) { for (ElementDescriptor e : grandparent.getChildren()) { if (e.getXmlName().startsWith(parent)) { - return grandparent.getChildren(); + return sort(grandparent.getChildren()); } } } @@ -281,6 +367,25 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { return null; } + /** Non-destructively sort a list of ElementDescriptors and return the result */ + private static ElementDescriptor[] sort(ElementDescriptor[] elements) { + if (elements != null && elements.length > 1) { + // Sort alphabetically. Must make copy to not destroy original. + ElementDescriptor[] copy = new ElementDescriptor[elements.length]; + System.arraycopy(elements, 0, copy, 0, elements.length); + + Arrays.sort(copy, new Comparator<ElementDescriptor>() { + public int compare(ElementDescriptor e1, ElementDescriptor e2) { + return e1.getXmlLocalName().compareTo(e2.getXmlLocalName()); + } + }); + + return copy; + } + + return elements; + } + /** * Gets the choices when the user is editing an XML attribute. * <p/> @@ -302,13 +407,14 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * a String[] if the user is editing an attribute value with some known values, * or null if nothing is known about the context. */ - private Object[] getChoicesForAttribute(String parent, - Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo) { + private Object[] getChoicesForAttribute( + String parent, Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo, + String wordPrefix) { Object[] choices = null; if (attrInfo.isInValue) { // Editing an attribute's value... Get the attribute name and then the // possible choices for the tuple(parent,attribute) - String value = attrInfo.value; + String value = attrInfo.valuePrefix; if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$ value = value.substring(1); // The prefix that was found at the beginning only scan for characters @@ -337,18 +443,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } if (currAttrNode != null) { - choices = currAttrNode.getPossibleValues(value); - - if (currAttrNode instanceof UiFlagAttributeNode) { - // A "flag" can consist of several values separated by "or" (|). - // If the correct prefix contains such a pipe character, we change - // it so that only the currently edited value is completed. - pos = value.indexOf('|'); - if (pos >= 0) { - attrInfo.correctedPrefix = value = value.substring(pos + 1); - attrInfo.needTag = 0; - } - } + choices = getAttributeValueChoices(currAttrNode, attrInfo, value); } } @@ -385,6 +480,85 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { return choices; } + protected Object[] getAttributeValueChoices(UiAttributeNode currAttrNode, AttribInfo attrInfo, + String value) { + Object[] choices; + int pos; + choices = currAttrNode.getPossibleValues(value); + if (choices != null && currAttrNode instanceof UiResourceAttributeNode) { + attrInfo.skipEndTag = false; + } + + if (currAttrNode instanceof UiFlagAttributeNode) { + // A "flag" can consist of several values separated by "or" (|). + // If the correct prefix contains such a pipe character, we change + // it so that only the currently edited value is completed. + pos = value.lastIndexOf('|'); + if (pos >= 0) { + attrInfo.correctedPrefix = value = value.substring(pos + 1); + attrInfo.needTag = 0; + } + + attrInfo.skipEndTag = false; + } + + // Should we do suffix completion on dimension units etc? + choices = completeSuffix(choices, value, currAttrNode); + + // Check to see if the user is attempting resource completion + AttributeDescriptor attributeDescriptor = currAttrNode.getDescriptor(); + IAttributeInfo attributeInfo = attributeDescriptor.getAttributeInfo(); + if (value.startsWith(PREFIX_RESOURCE_REF) + && !Format.REFERENCE.in(attributeInfo.getFormats())) { + // Special case: If the attribute value looks like a reference to a + // resource, offer to complete it, since in many cases our metadata + // does not correctly state whether a resource value is allowed. We don't + // offer these for an empty completion context, but if the user has + // actually typed "@", in that case list resource matches. + // For example, for android:minHeight this makes completion on @dimen/ + // possible. + choices = UiResourceAttributeNode.computeResourceStringMatches( + mEditor, attributeDescriptor, value); + attrInfo.skipEndTag = false; + } + + return choices; + } + + protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset, + String parentTagName, String attributeName, Node node, String wordPrefix, + boolean skipEndTag, int replaceLength) { + } + + protected void computeTextValues(List<ICompletionProposal> proposals, int offset, + Node parentNode, Node currentNode, UiElementNode uiParent, + String wordPrefix) { + + if (parentNode != null) { + // Examine the parent of the text node. + Object[] choices = getElementChoicesForTextNode(parentNode); + if (choices != null && choices.length > 0) { + ISourceViewer viewer = mEditor.getStructuredSourceViewer(); + char needTag = computeElementNeedTag(viewer, offset, wordPrefix); + + // get the selection length + int selectionLength = 0; + ISelection selection = viewer.getSelectionProvider().getSelection(); + if (selection instanceof TextSelection) { + TextSelection textSelection = (TextSelection) selection; + selectionLength = textSelection.getLength(); + } + int replaceLength = 0; + addMatchingProposals(proposals, choices, + offset, parentNode != null ? parentNode : null, wordPrefix, needTag, + false /* isAttribute */, + false /*isNew*/, + false /*isComplete*/, + selectionLength + replaceLength); + } + } + } + /** * Gets the choices when the user is editing an XML text node. * <p/> @@ -394,42 +568,46 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * * @return An ElementDescriptor[] or null. */ - private Object[] getChoicesForTextNode(Node currentNode) { + private Object[] getElementChoicesForTextNode(Node parentNode) { Object[] choices = null; String parent; - Node parent_node = currentNode.getParentNode(); - if (parent_node.getNodeType() == Node.ELEMENT_NODE) { + if (parentNode.getNodeType() == Node.ELEMENT_NODE) { // We're editing a text node which parent is an element node. Limit // content assist to elements valid for the parent. - parent = parent_node.getNodeName(); + parent = parentNode.getNodeName(); ElementDescriptor desc = getDescriptor(parent); if (desc != null) { - choices = desc.getChildren(); + choices = sort(desc.getChildren()); } - } else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) { + } else if (parentNode.getNodeType() == Node.DOCUMENT_NODE) { // We're editing a text node at the first level (i.e. root node). // Limit content assist to the only valid root elements. - choices = getRootDescriptor().getChildren(); + choices = sort(getRootDescriptor().getChildren()); } + return choices; } - /** - * Given a list of choices found, generates the proposals to be displayed to the user. + /** + * Given a list of choices, adds in any that match the current prefix into the + * proposals list. * <p/> * Choices is an object array. Items of the array can be: * - ElementDescriptor: a possible element descriptor which XML name should be completed. * - AttributeDescriptor: a possible attribute descriptor which XML name should be completed. * - String: string values to display as-is to the user. Typically those are possible * values for a given attribute. - * - * @return The ICompletionProposal[] to display to the user. + * - Pair of Strings: the first value is the keyword to insert, and the second value + * is the tooltip/help for the value to be displayed in the documentation popup. */ - private ICompletionProposal[] computeProposals(int offset, Node currentNode, - Object[] choices, String wordPrefix, char need_tag, - boolean is_attribute, int selectionLength) { - ArrayList<CompletionProposal> proposals = new ArrayList<CompletionProposal>(); - HashMap<String, String> nsUriMap = new HashMap<String, String>(); + protected void addMatchingProposals(List<ICompletionProposal> proposals, Object[] choices, + int offset, Node currentNode, String wordPrefix, char needTag, + boolean isAttribute, boolean isNew, boolean skipEndTag, int replaceLength) { + if (choices == null) { + return; + } + + Map<String, String> nsUriMap = new HashMap<String, String>(); for (Object choice : choices) { String keyword = null; @@ -438,7 +616,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { String tooltip = null; if (choice instanceof ElementDescriptor) { keyword = ((ElementDescriptor)choice).getXmlName(); - icon = ((ElementDescriptor)choice).getIcon(); + icon = ((ElementDescriptor)choice).getGenericIcon(); tooltip = DescriptorsUtils.formatTooltip(((ElementDescriptor)choice).getTooltip()); } else if (choice instanceof TextValueDescriptor) { continue; // Value nodes are not part of the completion choices @@ -446,7 +624,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { continue; // not real attribute descriptors } else if (choice instanceof AttributeDescriptor) { keyword = ((AttributeDescriptor)choice).getXmlLocalName(); - icon = ((AttributeDescriptor)choice).getIcon(); + icon = ((AttributeDescriptor)choice).getGenericIcon(); if (choice instanceof TextAttributeDescriptor) { tooltip = ((TextAttributeDescriptor) choice).getTooltip(); } @@ -467,49 +645,180 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } else if (choice instanceof String) { keyword = (String) choice; + if (isAttribute) { + icon = IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME); + } + } else if (choice instanceof Pair<?, ?>) { + @SuppressWarnings("unchecked") + Pair<String, String> pair = (Pair<String, String>) choice; + keyword = pair.getFirst(); + tooltip = pair.getSecond(); + if (isAttribute) { + icon = IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME); + } } else { continue; // discard unknown choice } String nsKeyword = nsPrefix == null ? keyword : (nsPrefix + keyword); - if (keyword.startsWith(wordPrefix) || - (nsPrefix != null && keyword.startsWith(nsPrefix)) || - (nsPrefix != null && nsKeyword.startsWith(wordPrefix))) { - if (nsPrefix != null) { - keyword = nsPrefix + keyword; - } - String end_tag = ""; //$NON-NLS-1$ - if (need_tag != 0) { - if (need_tag == '"') { - keyword = need_tag + keyword; - end_tag = String.valueOf(need_tag); - } else if (need_tag == '<') { + if (nameStartsWith(nsKeyword, wordPrefix, nsPrefix)) { + keyword = nsKeyword; + String endTag = ""; //$NON-NLS-1$ + if (needTag != 0) { + if (needTag == '"') { + keyword = needTag + keyword; + endTag = String.valueOf(needTag); + } else if (needTag == '<') { if (elementCanHaveChildren(choice)) { - end_tag = String.format("></%1$s>", keyword); //$NON-NLS-1$ - keyword = need_tag + keyword; + endTag = String.format("></%1$s>", keyword); //$NON-NLS-1$ } else { - keyword = need_tag + keyword; - end_tag = "/>"; //$NON-NLS-1$ + endTag = "/>"; //$NON-NLS-1$ } + keyword = needTag + keyword + ' '; + } else if (needTag == ' ') { + keyword = needTag + keyword; + } + } else if (!isAttribute && isNew) { + if (elementCanHaveChildren(choice)) { + endTag = String.format("></%1$s>", keyword); //$NON-NLS-1$ + } else { + endTag = "/>"; //$NON-NLS-1$ } + keyword = keyword + ' '; } - CompletionProposal proposal = new CompletionProposal( - keyword + end_tag, // String replacementString - offset - wordPrefix.length(), // int replacementOffset - wordPrefix.length() + selectionLength, // int replacementLength - keyword.length(), // int cursorPosition (rel. to rplcmntOffset) - icon, // Image image - null, // String displayString - null, // IContextInformation contextInformation - tooltip // String additionalProposalInfo - ); - - proposals.add(proposal); + + final String suffix; + int cursorPosition; + final String displayString; + if (choice instanceof AttributeDescriptor && isNew) { + // Special case for attributes: insert ="" stuff and locate caret inside "" + suffix = "=\"\""; //$NON-NLS-1$ + cursorPosition = keyword.length() + suffix.length() - 1; + displayString = keyword + endTag; // don't include suffix; + } else { + suffix = endTag; + cursorPosition = keyword.length(); + displayString = null; + } + + if (skipEndTag) { + assert isAttribute; + cursorPosition++; + } + + // For attributes, automatically insert ns:attribute="" and place the cursor + // inside the quotes. + // Special case for attributes: insert ="" stuff and locate caret inside "" + proposals.add(new CompletionProposal( + keyword + suffix , // String replacementString + offset - wordPrefix.length(), // int replacementOffset + wordPrefix.length() + replaceLength,// int replacementLength + cursorPosition, // cursorPosition + icon, // Image image + displayString, // displayString + null, // IContextInformation contextInformation + tooltip // String additionalProposalInfo + )); } } + } - return proposals.toArray(new ICompletionProposal[proposals.size()]); + /** + * Returns true if the given word starts with the given prefix. The comparison is not + * case sensitive. + * + * @param word the word to test + * @param prefix the prefix the word should start with + * @return true if the given word starts with the given prefix + */ + protected static boolean startsWith(String word, String prefix) { + int prefixLength = prefix.length(); + int wordLength = word.length(); + if (wordLength < prefixLength) { + return false; + } + + for (int i = 0; i < prefixLength; i++) { + if (Character.toLowerCase(prefix.charAt(i)) + != Character.toLowerCase(word.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * This method performs a prefix match for the given word and prefix, with a couple of + * Android code completion specific twists: + * <ol> + * <li> The match is not case sensitive, so {word="fOo",prefix="FoO"} is a match. + * <li>If the word to be matched has a namespace prefix, the typed prefix doesn't have + * to match it. So {word="android:foo", prefix="foo"} is a match. + * <li>If the attribute name part starts with "layout_" it can be omitted. So + * {word="android:layout_marginTop",prefix="margin"} is a match, as is + * {word="android:layout_marginTop",prefix="android:margin"}. + * </ol> + * + * @param word the full word to be matched, including namespace if any + * @param prefix the prefix to check + * @param nsPrefix the namespace prefix (android: or local definition of android + * namespace prefix) + * @return true if the prefix matches for code completion + */ + protected static boolean nameStartsWith(String word, String prefix, String nsPrefix) { + if (nsPrefix == null) { + nsPrefix = ""; //$NON-NLS-1$ + } + + int wordStart = nsPrefix.length(); + int prefixStart = 0; + + if (startsWith(prefix, nsPrefix)) { + // Already matches up through the namespace prefix: + prefixStart = wordStart; + } else if (startsWith(nsPrefix, prefix)) { + return true; + } + + int prefixLength = prefix.length(); + int wordLength = word.length(); + + if (wordLength - wordStart < prefixLength - prefixStart) { + return false; + } + + boolean matches = true; + for (int i = prefixStart, j = wordStart; i < prefixLength; i++, j++) { + char c1 = Character.toLowerCase(prefix.charAt(i)); + char c2 = Character.toLowerCase(word.charAt(j)); + if (c1 != c2) { + matches = false; + break; + } + } + + if (!matches && word.startsWith(ATTR_LAYOUT_PREFIX, wordStart) + && !prefix.startsWith(ATTR_LAYOUT_PREFIX, prefixStart)) { + wordStart += ATTR_LAYOUT_PREFIX.length(); + + if (wordLength - wordStart < prefixLength - prefixStart) { + return false; + } + + for (int i = prefixStart, j = wordStart; i < prefixLength; i++, j++) { + char c1 = Character.toLowerCase(prefix.charAt(i)); + char c2 = Character.toLowerCase(word.charAt(j)); + if (c1 != c2) { + return false; + } + } + + return true; + } + + return matches; } /** @@ -521,7 +830,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * or if one of the attributes is a TextValueDescriptor. * * @param descriptor An ElementDescriptor or an AttributeDescriptor - * @return True if the descriptor is an ElementDescriptor that can have children or a text value + * @return True if the descriptor is an ElementDescriptor that can have children or a text + * value */ private boolean elementCanHaveChildren(Object descriptor) { if (descriptor instanceof ElementDescriptor) { @@ -529,8 +839,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (desc.hasChildren()) { return true; } - for (AttributeDescriptor attr_desc : desc.getAttributes()) { - if (attr_desc instanceof TextValueDescriptor) { + for (AttributeDescriptor attrDesc : desc.getAttributes()) { + if (attrDesc instanceof TextValueDescriptor) { return true; } } @@ -637,25 +947,53 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } /** + * Search forward and find the first non-space character and return it. Returns 0 if no + * such character was found. + */ + private char nextNonspaceChar(ITextViewer viewer, int offset) { + IDocument document = viewer.getDocument(); + int length = document.getLength(); + for (; offset < length; offset++) { + try { + char c = document.getChar(offset); + if (!Character.isWhitespace(c)) { + return c; + } + } catch (BadLocationException e) { + return 0; + } + } + + return 0; + } + + /** * Information about the current edit of an attribute as reported by parseAttributeInfo. */ - private class AttribInfo { + protected static class AttribInfo { + public AttribInfo() { + } + /** True if the cursor is located in an attribute's value, false if in an attribute name */ public boolean isInValue = false; /** The attribute name. Null when not set. */ public String name = null; - /** The attribute value. Null when not set. The value *may* start with a quote - * (' or "), in which case we know we don't need to quote the string for the user */ - public String value = null; + /** The attribute value top the left of the cursor. Null when not set. The value + * *may* start with a quote (' or "), in which case we know we don't need to quote + * the string for the user */ + public String valuePrefix = null; /** String typed by the user so far (i.e. right before requesting code completion), * which will be corrected if we find a possible completion for an attribute value. * See the long comment in getChoicesForAttribute(). */ public String correctedPrefix = null; /** Non-zero if an attribute value need a start/end tag (i.e. quotes or brackets) */ public char needTag = 0; + /** Number of characters to replace after the prefix */ + public int replaceLength = 0; + /** Should the cursor advance through the end tag when inserted? */ + public boolean skipEndTag = false; } - /** * Try to guess if the cursor is editing an element's name or an attribute following an * element. If it's an attribute, try to find if an attribute name is being defined or @@ -669,16 +1007,24 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { * @return An AttribInfo describing which attribute is being edited or null if the cursor is * not editing an attribute (in which case it must be an element's name). */ - private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset) { + private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset, int prefixStartOffset) { AttribInfo info = new AttribInfo(); + int originalOffset = offset; IDocument document = viewer.getDocument(); int n = document.getLength(); if (offset <= n) { try { + // Look to the right to make sure we aren't sitting on the boundary of the + // beginning of a new element with whitespace before it + if (offset < n && document.getChar(offset) == '<') { + return null; + } + n = offset; for (;offset > 0; --offset) { char ch = document.getChar(offset - 1); + if (ch == '>') break; if (ch == '<') break; } @@ -711,6 +1057,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$ } while(!temp.equals(text)); + IRegion lineInfo = document.getLineInformationOfOffset(originalOffset); + int lineStart = lineInfo.getOffset(); + String line = document.get(lineStart, lineInfo.getLength()); + int cursorColumn = originalOffset - lineStart; + int prefixLength = originalOffset - prefixStartOffset; + // Now we're left with 3 cases: // - nothing: either there is no attribute definition or the cursor located after // a completed attribute definition. @@ -718,14 +1070,89 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { // merged with the previous one. // - string with an = sign, optionally followed by a quote (' or "): the user is // writing the value of the attribute. - int pos_equal = text.indexOf('='); - if (pos_equal == -1) { + int posEqual = text.indexOf('='); + if (posEqual == -1) { info.isInValue = false; info.name = text.trim(); + + // info.name is currently just the prefix of the attribute name. + // Look at the text buffer to find the complete name (since we need + // to know its bounds in order to replace it when a different attribute + // that matches this prefix is chosen) + int nameStart = cursorColumn; + for (int nameEnd = nameStart; nameEnd < line.length(); nameEnd++) { + char c = line.charAt(nameEnd); + if (!(Character.isLetter(c) || c == ':' || c == '_')) { + String nameSuffix = line.substring(nameStart, nameEnd); + info.name = text.trim() + nameSuffix; + break; + } + } + + info.replaceLength = info.name.length() - prefixLength; + + if (info.name.length() == 0 && originalOffset > 0) { + // Ensure that attribute names are properly separated + char prevChar = extractChar(viewer, originalOffset - 1); + if (prevChar == '"' || prevChar == '\'') { + // Ensure that the attribute is properly separated from the + // previous element + info.needTag = ' '; + } + } + info.skipEndTag = false; } else { info.isInValue = true; - info.name = text.substring(0, pos_equal).trim(); - info.value = text.substring(pos_equal + 1).trim(); + info.name = text.substring(0, posEqual).trim(); + info.valuePrefix = text.substring(posEqual + 1); + + char quoteChar = '"'; // Does " or ' surround the XML value? + for (int i = posEqual + 1; i < text.length(); i++) { + if (!Character.isWhitespace(text.charAt(i))) { + quoteChar = text.charAt(i); + break; + } + } + + // Must compute the complete value + int valueStart = cursorColumn; + int valueEnd = valueStart; + for (; valueEnd < line.length(); valueEnd++) { + char c = line.charAt(valueEnd); + if (c == quoteChar) { + // Make sure this isn't the *opening* quote of the value, + // which is the case if we invoke code completion with the + // caret between the = and the opening quote; in that case + // we consider it value completion, and offer items including + // the quotes, but we shouldn't bail here thinking we have found + // the end of the value. + // Look backwards to make sure we find another " before + // we find a = + boolean isFirst = false; + for (int j = valueEnd - 1; j >= 0; j--) { + char pc = line.charAt(j); + if (pc == '=') { + isFirst = true; + break; + } else if (pc == quoteChar) { + valueStart = j; + break; + } + } + if (!isFirst) { + info.skipEndTag = true; + break; + } + } + } + int valueEndOffset = valueEnd + lineStart; + info.replaceLength = valueEndOffset - (prefixStartOffset + prefixLength); + // Is the caret to the left of the value quote? If so, include it in + // the replace length. + int valueStartOffset = valueStart + lineStart; + if (valueStartOffset == prefixStartOffset && valueEnd > valueStart) { + info.replaceLength++; + } } return info; } catch (BadLocationException e) { @@ -736,12 +1163,9 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { return null; } - - /** - * Returns the XML DOM node corresponding to the given offset of the given document. - */ - public static Node getNode(ITextViewer viewer, int offset) { - return AndroidXmlEditor.getNode(viewer.getDocument(), offset); + /** Returns the root descriptor id to use */ + protected int getRootDescriptorId() { + return mDescriptorId; } /** @@ -751,7 +1175,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { if (mRootDescriptor == null) { AndroidTargetData data = mEditor.getTargetData(); if (data != null) { - IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId); + IDescriptorProvider descriptorProvider = + data.getDescriptorProvider(getRootDescriptorId()); if (descriptorProvider != null) { mRootDescriptor = new ElementDescriptor("", //$NON-NLS-1$ @@ -764,26 +1189,131 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { } /** - * Returns the active {@link AndroidXmlEditor} matching this source viewer. + * Fixed list of dimension units, along with user documentation, for use by + * {@link #completeSuffix}. */ - private AndroidXmlEditor getAndroidXmlEditor(ITextViewer viewer) { - IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (wwin != null) { - IWorkbenchPage page = wwin.getActivePage(); - if (page != null) { - IEditorPart editor = page.getActiveEditor(); - if (editor instanceof AndroidXmlEditor) { - ISourceViewer ssviewer = ((AndroidXmlEditor) editor).getStructuredSourceViewer(); - if (ssviewer == viewer) { - return (AndroidXmlEditor) editor; + private static final String[] sDimensionUnits = new String[] { + "dp", //$NON-NLS-1$ + "<b>Density-independent Pixels</b> - an abstract unit that is based on the physical " + + "density of the screen.", + + "sp", //$NON-NLS-1$ + "<b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by " + + "the user's font size preference.", + + "pt", //$NON-NLS-1$ + "<b>Points</b> - 1/72 of an inch based on the physical size of the screen.", + + "mm", //$NON-NLS-1$ + "<b>Millimeters</b> - based on the physical size of the screen.", + + "in", //$NON-NLS-1$ + "<b>Inches</b> - based on the physical size of the screen.", + + "px", //$NON-NLS-1$ + "<b>Pixels</b> - corresponds to actual pixels on the screen. Not recommended.", + }; + + /** + * Fixed list of fractional units, along with user documentation, for use by + * {@link #completeSuffix} + */ + private static final String[] sFractionUnits = new String[] { + "%", //$NON-NLS-1$ + "<b>Fraction</b> - a percentage of the base size", + + "%p", //$NON-NLS-1$ + "<b>Fraction</b> - a percentage relative to parent container", + }; + + /** + * Completes suffixes for applicable types (like dimensions and fractions) such that + * after a dimension number you get completion on unit types like "px". + */ + private Object[] completeSuffix(Object[] choices, String value, UiAttributeNode currAttrNode) { + IAttributeInfo attributeInfo = currAttrNode.getDescriptor().getAttributeInfo(); + Format[] formats = attributeInfo.getFormats(); + List<Object> suffixes = new ArrayList<Object>(); + + if (value.length() > 0 && Character.isDigit(value.charAt(0))) { + boolean hasDimension = Format.DIMENSION.in(formats); + boolean hasFraction = Format.FRACTION.in(formats); + + if (hasDimension || hasFraction) { + // Split up the value into a numeric part (the prefix) and the + // unit part (the suffix) + int suffixBegin = 0; + for (; suffixBegin < value.length(); suffixBegin++) { + if (!Character.isDigit(value.charAt(suffixBegin))) { + break; + } + } + String number = value.substring(0, suffixBegin); + String suffix = value.substring(suffixBegin); + + // Add in the matching dimension and/or fraction units, if any + if (hasDimension) { + // Each item has two entries in the array of strings: the first odd numbered + // ones are the unit names and the second even numbered ones are the + // corresponding descriptions. + for (int i = 0; i < sDimensionUnits.length; i += 2) { + String unit = sDimensionUnits[i]; + if (startsWith(unit, suffix)) { + String description = sDimensionUnits[i + 1]; + suffixes.add(Pair.of(number + unit, description)); + } + } + + // Allow "dip" completion but don't offer it ("dp" is preferred) + if (startsWith(suffix, "di") || startsWith(suffix, "dip")) { //$NON-NLS-1$ //$NON-NLS-2$ + suffixes.add(Pair.of(number + "dip", "Alternative name for \"dp\"")); //$NON-NLS-1$ + } + } + if (hasFraction) { + for (int i = 0; i < sFractionUnits.length; i += 2) { + String unit = sFractionUnits[i]; + if (startsWith(unit, suffix)) { + String description = sFractionUnits[i + 1]; + suffixes.add(Pair.of(number + unit, description)); + } } } } } - return null; - } - + boolean hasFlag = Format.FLAG.in(formats); + if (hasFlag) { + boolean isDone = false; + String[] flagValues = attributeInfo.getFlagValues(); + for (String flagValue : flagValues) { + if (flagValue.equals(value)) { + isDone = true; + break; + } + } + if (isDone) { + // Add in all the new values with a separator of | + String currentValue = currAttrNode.getCurrentValue(); + for (String flagValue : flagValues) { + if (currentValue == null || !currentValue.contains(flagValue)) { + suffixes.add(value + '|' + flagValue); + } + } + } + } + if (suffixes.size() > 0) { + // Merge previously added choices (from attribute enums etc) with the new matches + List<Object> all = new ArrayList<Object>(); + if (choices != null) { + for (Object s : choices) { + all.add(s); + } + } + all.addAll(suffixes); + choices = all.toArray(); + } + return choices; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidSourceViewerConfig.java index 38d8844..5a38fec 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidSourceViewerConfig.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidSourceViewerConfig.java @@ -18,23 +18,28 @@ package com.android.ide.eclipse.adt.internal.editors; import org.eclipse.jface.text.IAutoEditStrategy; -import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContentAssistant; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.jface.text.formatter.IContentFormatter; import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.jface.viewers.IInputProvider; import org.eclipse.wst.sse.core.text.IStructuredPartitions; import org.eclipse.wst.xml.core.text.IXMLPartitions; import org.eclipse.wst.xml.ui.StructuredTextViewerConfigurationXML; +import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistProcessor; import java.util.ArrayList; +import java.util.List; import java.util.Map; /** * Base Source Viewer Configuration for Android resources. */ +@SuppressWarnings("restriction") // XMLContentAssistProcessor public class AndroidSourceViewerConfig extends StructuredTextViewerConfigurationXML { /** Content Assist Processor to use for all handled partitions. */ @@ -67,17 +72,6 @@ public class AndroidSourceViewerConfig extends StructuredTextViewerConfiguration if (partitionType == IStructuredPartitions.UNKNOWN_PARTITION || partitionType == IStructuredPartitions.DEFAULT_PARTITION || partitionType == IXMLPartitions.XML_DEFAULT) { - if (sourceViewer instanceof IInputProvider) { - IInputProvider input = (IInputProvider) sourceViewer; - Object a = input.getInput(); - if (a != null) - a.toString(); - } - - IDocument doc = sourceViewer.getDocument(); - if (doc != null) - doc.toString(); - processors.add(mProcessor); } @@ -85,7 +79,13 @@ public class AndroidSourceViewerConfig extends StructuredTextViewerConfiguration partitionType); if (others != null && others.length > 0) { for (IContentAssistProcessor p : others) { - processors.add(p); + // Builtin Eclipse WTP code completion assistant? If so, + // wrap it with our own filter which hides some unwanted completions. + if (p instanceof XMLContentAssistProcessor) { + processors.add(new FilteringContentAssistProcessor(p)); + } else { + processors.add(p); + } } } @@ -125,4 +125,70 @@ public class AndroidSourceViewerConfig extends StructuredTextViewerConfiguration return targets; } + /** + * A delegating {@link IContentAssistProcessor} whose purpose is to filter out some + * default Eclipse XML completions which are distracting in Android XML files + */ + private class FilteringContentAssistProcessor implements IContentAssistProcessor { + private IContentAssistProcessor mDelegate; + + public FilteringContentAssistProcessor(IContentAssistProcessor delegate) { + super(); + mDelegate = delegate; + } + + public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { + ICompletionProposal[] result = mDelegate.computeCompletionProposals(viewer, offset); + if (result == null) { + return null; + } + + List<ICompletionProposal> proposals = + new ArrayList<ICompletionProposal>(result.length); + for (ICompletionProposal proposal : result) { + String replacement = proposal.getDisplayString(); + if (replacement.charAt(0) == '"' && + replacement.charAt(replacement.length() - 1) == '"') { + // Filter out attribute values. In Android XML files (where there is no DTD + // etc) the default Eclipse XML code completion simply provides the + // existing value as a completion. This is often misleading, since if you + // for example have a typo, completion will show your current (wrong) + // value as a valid completion. + } else if (replacement.contains("Namespace") //$NON-NLS-1$ + || replacement.startsWith("XSL ") //$NON-NLS-1$ + || replacement.contains("Schema")) { //$NON-NLS-1$ + // Eclipse adds in a number of namespace and schema related completions which + // are not usually applicable in our files. + } else { + proposals.add(proposal); + } + } + + if (proposals.size() == result.length) { + return result; + } else { + return proposals.toArray(new ICompletionProposal[proposals.size()]); + } + } + + public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { + return mDelegate.computeContextInformation(viewer, offset); + } + + public char[] getCompletionProposalAutoActivationCharacters() { + return mDelegate.getCompletionProposalAutoActivationCharacters(); + } + + public char[] getContextInformationAutoActivationCharacters() { + return mDelegate.getContextInformationAutoActivationCharacters(); + } + + public IContextInformationValidator getContextInformationValidator() { + return mDelegate.getContextInformationValidator(); + } + + public String getErrorMessage() { + return mDelegate.getErrorMessage(); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java index 89edee4..bd12e27 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java @@ -40,6 +40,7 @@ import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.widgets.Display; @@ -49,7 +50,9 @@ import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.browser.IWorkbenchBrowserSupport; import org.eclipse.ui.forms.IManagedForm; @@ -89,6 +92,9 @@ import java.net.URL; @SuppressWarnings("restriction") // Uses XML model, which has no non-restricted replacement yet public abstract class AndroidXmlEditor extends FormEditor implements IResourceChangeListener { + /** Icon used for the XML source page. */ + public static final String ICON_XML_PAGE = "editor_page_source"; //$NON-NLS-1$ + /** Preference name for the current page of this file */ private static final String PREF_CURRENT_PAGE = "_current_page"; //$NON-NLS-1$ @@ -604,12 +610,53 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh */ private void createTextEditor() { try { + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log( + IStatus.ERROR, + "%s.createTextEditor: input=%s %s", + this.getClass(), + getEditorInput() == null ? "null" : getEditorInput().getClass(), + getEditorInput() == null ? "null" : getEditorInput().toString() + ); + + org.eclipse.core.runtime.IAdaptable adaptable= getEditorInput(); + IFile file1 = (IFile)adaptable.getAdapter(IFile.class); + org.eclipse.core.runtime.IPath location= file1.getFullPath(); + org.eclipse.core.resources.IWorkspaceRoot workspaceRoot= ResourcesPlugin.getWorkspace().getRoot(); + IFile file2 = workspaceRoot.getFile(location); + + try { + org.eclipse.core.runtime.content.IContentDescription desc = file2.getContentDescription(); + org.eclipse.core.runtime.content.IContentType type = desc.getContentType(); + + AdtPlugin.log(IStatus.ERROR, + "file %s description %s %s; contentType %s %s", + file2, + desc == null ? "null" : desc.getClass(), + desc == null ? "null" : desc.toString(), + type == null ? "null" : type.getClass(), + type == null ? "null" : type.toString()); + + } catch (CoreException e) { + e.printStackTrace(); + } + } + mTextEditor = new StructuredTextEditor(); int index = addPage(mTextEditor, getEditorInput()); mTextPageIndex = index; setPageText(index, mTextEditor.getTitle()); setPageImage(index, - IconFactory.getInstance().getIcon("editor_page_source")); //$NON-NLS-1$ + IconFactory.getInstance().getIcon(ICON_XML_PAGE)); + + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.ERROR, "Found document class: %1$s, file=%2$s", + mTextEditor.getTextViewer().getDocument() != null ? + mTextEditor.getTextViewer().getDocument().getClass() : + "null", + getEditorInput() + ); + } if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) { Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, @@ -661,7 +708,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source * Editor) or null if not available. */ - public final IStructuredDocument getStructuredDocument() { + public IStructuredDocument getStructuredDocument() { if (mTextEditor != null && mTextEditor.getTextViewer() != null) { return (IStructuredDocument) mTextEditor.getTextViewer().getDocument(); } @@ -669,39 +716,6 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh } /** - * Returns the XML DOM node corresponding to the given offset of the given - * document. - * - * @param document The document to look in - * @param offset The offset to look up the node for - * @return The node containing the offset, or null - */ - @SuppressWarnings("restriction") // No replacement for restricted XML model yet - public static Node getNode(IDocument document, int offset) { - Node node = null; - IModelManager modelManager = StructuredModelManager.getModelManager(); - if (modelManager == null) { - return null; - } - try { - IStructuredModel model = modelManager.getExistingModelForRead(document); - if (model != null) { - try { - for (; offset >= 0 && node == null; --offset) { - node = (Node) model.getIndexedRegion(offset); - } - } finally { - model.releaseFromRead(); - } - } - } catch (Exception e) { - // Ignore exceptions. - } - - return node; - } - - /** * Returns a version of the model that has been shared for read. * <p/> * Callers <em>must</em> call model.releaseFromRead() when done, typically @@ -714,7 +728,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * * @return The model for the XML document or null if cannot be obtained from the editor */ - public final IStructuredModel getModelForRead() { + public IStructuredModel getModelForRead() { IStructuredDocument document = getStructuredDocument(); if (document != null) { IModelManager mm = StructuredModelManager.getModelManager(); @@ -743,7 +757,6 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * @return The model for the XML document or null if cannot be obtained from the editor */ private IStructuredModel getModelForEdit() { - IStructuredDocument document = getStructuredDocument(); if (document != null) { IModelManager mm = StructuredModelManager.getModelManager(); @@ -1139,8 +1152,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * @return The indent-string of the given node, or "" if the indentation for some reason could * not be computed. */ - public static String getIndent(IStructuredDocument document, Node xmlNode) { - assert xmlNode.getNodeType() == Node.ELEMENT_NODE; + public static String getIndent(IDocument document, Node xmlNode) { if (xmlNode instanceof IndexedRegion) { IndexedRegion region = (IndexedRegion)xmlNode; int startOffset = region.getStartOffset(); @@ -1158,7 +1170,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * @return The indent-string of the given node, or "" if the indentation for some * reason could not be computed. */ - public static String getIndentAtOffset(IStructuredDocument document, int offset) { + public static String getIndentAtOffset(IDocument document, int offset) { try { IRegion lineInformation = document.getLineInformationOfOffset(offset); if (lineInformation != null) { @@ -1186,6 +1198,32 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh } /** + * Returns the active {@link AndroidXmlEditor}, provided it matches the given source + * viewer + * + * @param viewer the source viewer to ensure the active editor is associated with + * @return the active editor provided it matches the given source viewer + */ + public static AndroidXmlEditor getAndroidXmlEditor(ITextViewer viewer) { + IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (wwin != null) { + IWorkbenchPage page = wwin.getActivePage(); + if (page != null) { + IEditorPart editor = page.getActiveEditor(); + if (editor instanceof AndroidXmlEditor) { + ISourceViewer ssviewer = + ((AndroidXmlEditor) editor).getStructuredSourceViewer(); + if (ssviewer == viewer) { + return (AndroidXmlEditor) editor; + } + } + } + } + + return null; + } + + /** * Listen to changes in the underlying XML model in the structured editor. */ private class XmlModelStateListener implements IModelStateListener { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimDescriptors.java new file mode 100644 index 0000000..dabab87 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimDescriptors.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.animator; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; + +import com.android.ide.common.resources.platform.DeclareStyleableInfo; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.sdklib.SdkConstants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Descriptors for the res/anim resources */ +public class AnimDescriptors implements IDescriptorProvider { + /** The root element descriptor */ + private ElementDescriptor mDescriptor; + /** The root element descriptors */ + private ElementDescriptor[] mRootDescriptors; + private Map<String, ElementDescriptor> nameToDescriptor; + + /** @return the root descriptor. */ + public ElementDescriptor getDescriptor() { + if (mDescriptor == null) { + mDescriptor = new ElementDescriptor("", getRootElementDescriptors()); //$NON-NLS-1$ + } + + return mDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mRootDescriptors; + } + + public ElementDescriptor getElementDescriptor(String mRootTag) { + if (nameToDescriptor == null) { + nameToDescriptor = new HashMap<String, ElementDescriptor>(); + for (ElementDescriptor descriptor : getRootElementDescriptors()) { + nameToDescriptor.put(descriptor.getXmlName(), descriptor); + } + } + + ElementDescriptor descriptor = nameToDescriptor.get(mRootTag); + if (descriptor == null) { + descriptor = getDescriptor(); + } + return descriptor; + } + + public synchronized void updateDescriptors(Map<String, DeclareStyleableInfo> styleMap) { + if (styleMap == null) { + return; + } + + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_NAME, + SdkConstants.NS_RESOURCES); + + List<ElementDescriptor> descriptors = new ArrayList<ElementDescriptor>(); + + String sdkUrl = + "http://developer.android.com/guide/topics/graphics/view-animation.html"; //$NON-NLS-1$ + ElementDescriptor set = AnimatorDescriptors.addElement(descriptors, styleMap, + "set", "Set", "AnimationSet", "Animation", //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$ + "A container that holds other animation elements (<alpha>, <scale>, " + + "<translate>, <rotate>) or other <set> elements. ", + sdkUrl, + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "alpha", "Alpha", "AlphaAnimation", "Animation", //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$ + "A fade-in or fade-out animation.", + sdkUrl, + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "scale", "Scale", "ScaleAnimation", "Animation", //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$ + "A resizing animation. You can specify the center point of the image from " + + "which it grows outward (or inward) by specifying pivotX and pivotY. " + + "For example, if these values are 0, 0 (top-left corner), all growth " + + "will be down and to the right.", + sdkUrl, + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "rotate", "Rotate", "RotateAnimation", "Animation", //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$ + "A rotation animation.", + sdkUrl, + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "translate", "Translate", "TranslateAnimation", "Animation", //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$ + "A vertical and/or horizontal motion. Supports the following attributes in " + + "any of the following three formats: values from -100 to 100 ending " + + "with \"%\", indicating a percentage relative to itself; values from " + + "-100 to 100 ending in \"%p\", indicating a percentage relative to its " + + "parent; a float value with no suffix, indicating an absolute value.", + sdkUrl, + xmlns, null, true /*mandatory*/); + + mRootDescriptors = descriptors.toArray(new ElementDescriptor[descriptors.size()]); + + // Allow <set> to nest the others (and other sets) + if (set != null) { + set.setChildren(mRootDescriptors); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java new file mode 100644 index 0000000..ee0c290 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.animator; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.common.api.IAttributeInfo.Format; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.resources.ResourceFolderType; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Content Assist Processor for /res/drawable XML files + */ +@VisibleForTesting +public final class AnimationContentAssist extends AndroidContentAssist { + private static final String OBJECT_ANIMATOR = "objectAnimator"; //$NON-NLS-1$ + private static final String PROPERTY_NAME = "propertyName"; //$NON-NLS-1$ + private static final String INTERPOLATOR_PROPERTY_NAME = "interpolator"; //$NON-NLS-1$ + private static final String INTERPOLATOR_NAME_SUFFIX = "_interpolator"; //$NON-NLS-1$ + + public AnimationContentAssist() { + super(AndroidTargetData.DESCRIPTOR_ANIMATOR); + } + + @Override + protected int getRootDescriptorId() { + String folderName = mEditor.getInputFile().getParent().getName(); + ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName); + if (folderType == ResourceFolderType.ANIM) { + return AndroidTargetData.DESCRIPTOR_ANIM; + } else { + return AndroidTargetData.DESCRIPTOR_ANIMATOR; + } + } + + @Override + protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset, + String parentTagName, String attributeName, Node node, String wordPrefix, + boolean skipEndTag, int replaceLength) { + + // Add value completion for the interpolator and propertyName attributes + + if (attributeName.endsWith(INTERPOLATOR_PROPERTY_NAME)) { + if (!wordPrefix.startsWith("@android:anim/")) { //$NON-NLS-1$ + // List all framework interpolators with full path first + AndroidTargetData data = mEditor.getTargetData(); + ResourceRepository repository = data.getFrameworkResources(); + List<String> interpolators = new ArrayList<String>(); + String base = '@' + ANDROID_PKG + ':' + ResourceType.ANIM.getName() + '/'; + for (ResourceItem item : repository.getResourceItemsOfType(ResourceType.ANIM)) { + String name = item.getName(); + if (name.endsWith(INTERPOLATOR_NAME_SUFFIX)) { + interpolators.add(base + item.getName()); + } + } + addMatchingProposals(proposals, interpolators.toArray(), offset, node, wordPrefix, + (char) 0 /* needTag */, true /* isAttribute */, false /* isNew */, + skipEndTag /* skipEndTag */, replaceLength); + } + + + super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node, + wordPrefix, skipEndTag, replaceLength); + } else if (parentTagName.equals(OBJECT_ANIMATOR) + && attributeName.endsWith(PROPERTY_NAME)) { + + // Special case: the user is code completing inside + // <objectAnimator propertyName="^"> + // In this case, offer ALL attribute names that make sense for animation + // (e.g. all numeric ones) + + String attributePrefix = wordPrefix; + if (startsWith(attributePrefix, ANDROID_NS_NAME_PREFIX)) { + attributePrefix = attributePrefix.substring(ANDROID_NS_NAME_PREFIX.length()); + } + + AndroidTargetData data = mEditor.getTargetData(); + if (data != null) { + IDescriptorProvider descriptorProvider = + data.getDescriptorProvider(AndroidTargetData.DESCRIPTOR_LAYOUT); + if (descriptorProvider != null) { + ElementDescriptor[] rootElementDescriptors = + descriptorProvider.getRootElementDescriptors(); + Map<String, AttributeDescriptor> matches = + new HashMap<String, AttributeDescriptor>(180); + for (ElementDescriptor elementDesc : rootElementDescriptors) { + for (AttributeDescriptor desc : elementDesc.getAttributes()) { + if (desc instanceof SeparatorAttributeDescriptor) { + continue; + } + String name = desc.getXmlLocalName(); + if (startsWith(name, attributePrefix)) { + for (Format f : desc.getAttributeInfo().getFormats()) { + if (f == Format.INTEGER || f == Format.FLOAT) { + // TODO: Filter out some common properties + // that the user probably isn't trying to + // animate: + // num*, min*, max*, *Index, *Threshold, + // *Duration, *Id, *Limit + + matches.put(name, desc); + break; + } + } + } + } + } + + List<AttributeDescriptor> sorted = + new ArrayList<AttributeDescriptor>(matches.size()); + sorted.addAll(matches.values()); + Collections.sort(sorted); + // Extract just the name+description pairs, since we don't want to + // use the full attribute descriptor (which forces the namespace + // prefix to be included) + List<Pair<String, String>> pairs = + new ArrayList<Pair<String, String>>(sorted.size()); + for (AttributeDescriptor d : sorted) { + pairs.add(Pair.of(d.getXmlLocalName(), d.getAttributeInfo().getJavaDoc())); + } + + addMatchingProposals(proposals, pairs.toArray(), offset, node, wordPrefix, + (char) 0 /* needTag */, true /* isAttribute */, false /* isNew */, + skipEndTag /* skipEndTag */, replaceLength); + return; + } + } + } else { + super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node, + wordPrefix, skipEndTag, replaceLength); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationEditor.java new file mode 100644 index 0000000..9f61238 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationEditor.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.animator; + +import static com.android.ide.eclipse.adt.AdtConstants.EDITORS_NAMESPACE; + +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.resources.ResourceFolderType; + +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Editor for /res/animator XML files. + */ +@SuppressWarnings("restriction") +public class AnimationEditor extends AndroidXmlEditor { + public static final String ID = EDITORS_NAMESPACE + ".animator.AnimationEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiRootNode; + /** The tag used at the root */ + private String mRootTag; + + public AnimationEditor() { + super(); + } + + @Override + public UiElementNode getUiRootNode() { + return mUiRootNode; + } + + @Override + public boolean isSaveAsAllowed() { + return true; + } + + @Override + protected void createFormPages() { + /* Disabled for now; doesn't work quite right + try { + addPage(new AnimatorTreePage(this)); + } catch (PartInitException e) { + AdtPlugin.log(IStatus.ERROR, "Error creating nested page"); //$NON-NLS-1$ + AdtPlugin.getDefault().getLog().log(e.getStatus()); + } + */ + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", + file.getName())); + } + } + + /** + * Processes the new XML Model. + * + * @param xmlDoc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xmlDoc) { + Element rootElement = xmlDoc.getDocumentElement(); + if (rootElement != null) { + mRootTag = rootElement.getTagName(); + } + + // create the ui root node on demand. + initUiRootNode(false /*force*/); + + if (mRootTag != null + && !mRootTag.equals(mUiRootNode.getDescriptor().getXmlLocalName())) { + AndroidTargetData data = getTargetData(); + if (data != null) { + ElementDescriptor descriptor; + if (getFolderType() == ResourceFolderType.ANIM) { + descriptor = data.getAnimDescriptors().getElementDescriptor(mRootTag); + } else { + descriptor = data.getAnimatorDescriptors().getElementDescriptor(mRootTag); + } + // Replace top level node now that we know the actual type + + // Disconnect from old + mUiRootNode.setEditor(null); + mUiRootNode.setXmlDocument(null); + + // Create new + mUiRootNode = descriptor.createUiNode(); + mUiRootNode.setXmlDocument(xmlDoc); + mUiRootNode.setEditor(this); + } + } + + if (mUiRootNode.getDescriptor() instanceof DocumentDescriptor) { + mUiRootNode.loadFromXmlNode(xmlDoc); + } else { + mUiRootNode.loadFromXmlNode(rootElement); + } + + super.xmlModelChanged(xmlDoc); + } + + @Override + protected void initUiRootNode(boolean force) { + // The manifest UI node is always created, even if there's no corresponding XML node. + if (mUiRootNode == null || force) { + ElementDescriptor descriptor; + boolean reload = false; + AndroidTargetData data = getTargetData(); + if (data == null) { + descriptor = new DocumentDescriptor("temp", null /*children*/); + } else { + if (getFolderType() == ResourceFolderType.ANIM) { + descriptor = data.getAnimDescriptors().getElementDescriptor(mRootTag); + } else { + descriptor = data.getAnimatorDescriptors().getElementDescriptor(mRootTag); + } + reload = true; + } + mUiRootNode = descriptor.createUiNode(); + mUiRootNode.setEditor(this); + + if (reload) { + onDescriptorsChanged(); + } + } + } + + private ResourceFolderType getFolderType() { + IFile inputFile = getInputFile(); + if (inputFile != null) { + String folderName = inputFile.getParent().getName(); + return ResourceFolderType.getFolderType(folderName); + } + return ResourceFolderType.ANIMATOR; + } + + private void onDescriptorsChanged() { + IStructuredModel model = getModelForRead(); + if (model != null) { + try { + Node node = getXmlDocument(model).getDocumentElement(); + mUiRootNode.reloadFromXmlNode(node); + } finally { + model.releaseFromRead(); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/IIdResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationSourceViewerConfig.java index acc4cf2..87a145b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/IIdResourceItem.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationSourceViewerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2007 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. @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources; +package com.android.ide.eclipse.adt.internal.editors.animator; -import com.android.resources.ResourceType; + +import com.android.ide.eclipse.adt.internal.editors.AndroidSourceViewerConfig; /** - * Classes which implements this interface provides a method indicating the state of a resource of - * type {@link ResourceType#ID}. + * Source Viewer Configuration for animation files. */ -public interface IIdResourceItem { - /** - * Returns whether the ID resource has been declared inline inside another resource XML file. - */ - public boolean isDeclaredInline(); +public class AnimationSourceViewerConfig extends AndroidSourceViewerConfig { + + public AnimationSourceViewerConfig() { + super(new AnimationContentAssist()); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimatorDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimatorDescriptors.java new file mode 100644 index 0000000..644aebb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimatorDescriptors.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.animator; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; + +import com.android.ide.common.resources.platform.DeclareStyleableInfo; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.sdklib.SdkConstants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Descriptors for /res/animator XML files. + */ +public class AnimatorDescriptors implements IDescriptorProvider { + /** The root element descriptor */ + private ElementDescriptor mDescriptor; + /** The root element descriptors */ + private ElementDescriptor[] mRootDescriptors; + private Map<String, ElementDescriptor> nameToDescriptor; + + /** @return the root descriptor. */ + public ElementDescriptor getDescriptor() { + if (mDescriptor == null) { + mDescriptor = new ElementDescriptor("", getRootElementDescriptors()); //$NON-NLS-1$ + } + + return mDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mRootDescriptors; + } + + public ElementDescriptor getElementDescriptor(String mRootTag) { + if (nameToDescriptor == null) { + nameToDescriptor = new HashMap<String, ElementDescriptor>(); + for (ElementDescriptor descriptor : getRootElementDescriptors()) { + nameToDescriptor.put(descriptor.getXmlName(), descriptor); + } + } + + ElementDescriptor descriptor = nameToDescriptor.get(mRootTag); + if (descriptor == null) { + descriptor = getDescriptor(); + } + return descriptor; + } + + public synchronized void updateDescriptors(Map<String, DeclareStyleableInfo> styleMap) { + if (styleMap == null) { + return; + } + + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_NAME, + SdkConstants.NS_RESOURCES); + + List<ElementDescriptor> descriptors = new ArrayList<ElementDescriptor>(); + + String sdkUrl = + "http://developer.android.com/guide/topics/graphics/animation.html"; //$NON-NLS-1$ + + ElementDescriptor set = addElement(descriptors, styleMap, + "set", "Animator Set", "AnimatorSet", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, sdkUrl, + xmlns, null, true /*mandatory*/); + + ElementDescriptor objectAnimator = addElement(descriptors, styleMap, + "objectAnimator", "Object Animator", //$NON-NLS-1$ + "PropertyAnimator", "Animator", //$NON-NLS-1$ //$NON-NLS-2$ + null /* tooltip */, sdkUrl, + xmlns, null, true /*mandatory*/); + + ElementDescriptor animator = addElement(descriptors, styleMap, + "animator", "Animator", "Animator", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, sdkUrl, + xmlns, null, true /*mandatory*/); + + mRootDescriptors = descriptors.toArray(new ElementDescriptor[descriptors.size()]); + + // Allow arbitrary nesting: the children of all of these element can include + // any of the others + if (objectAnimator != null) { + objectAnimator.setChildren(mRootDescriptors); + } + if (animator != null) { + animator.setChildren(mRootDescriptors); + } + if (set != null) { + set.setChildren(mRootDescriptors); + } + } + + /** + * Looks up the given style, and if found creates a new {@link ElementDescriptor} + * corresponding to the style. It can optionally take an extra style to merge in + * additional attributes from, and an extra attribute to add in as well. The new + * element, if it exists, can also be optionally added into a list. + * + * @param descriptors an optional list to add the element into, or null + * @param styleMap The map style => attributes from the attrs.xml file + * @param xmlName the XML tag name to use for the element + * @param uiName the UI name to display the element as + * @param styleName the name of the style which must exist for this style + * @param extraStyle an optional extra style to merge in attributes from, or null + * @param tooltip the tooltip or documentation for this element, or null + * @param sdkUrl an optional SDK url to display for the element, or null + * @param extraAttribute an extra attribute to add to the attributes list, or null + * @param childrenElements an array of children allowed by this element, or null + * @param mandatory if true, this element is mandatory + * @return a newly created element, or null if the style does not exist + */ + public static ElementDescriptor addElement( + List<ElementDescriptor> descriptors, + Map<String, DeclareStyleableInfo> styleMap, + String xmlName, String uiName, String styleName, String extraStyle, + String tooltip, String sdkUrl, + AttributeDescriptor extraAttribute, + ElementDescriptor[] childrenElements, + boolean mandatory) { + DeclareStyleableInfo style = styleMap.get(styleName); + if (style == null) { + return null; + } + ElementDescriptor element = new ElementDescriptor(xmlName, uiName, tooltip, sdkUrl, + null, childrenElements, mandatory); + + ArrayList<AttributeDescriptor> descs = new ArrayList<AttributeDescriptor>(); + + DescriptorsUtils.appendAttributes(descs, + null, // elementName + SdkConstants.NS_RESOURCES, + style.getAttributes(), + null, // requiredAttributes + null); // overrides + element.setTooltip(style.getJavaDoc()); + + if (extraStyle != null) { + style = styleMap.get(extraStyle); + if (style != null) { + DescriptorsUtils.appendAttributes(descs, + null, // elementName + SdkConstants.NS_RESOURCES, + style.getAttributes(), + null, // requiredAttributes + null); // overrides + } + } + + if (extraAttribute != null) { + descs.add(extraAttribute); + } + + element.setAttributes(descs.toArray(new AttributeDescriptor[descs.size()])); + if (descriptors != null) { + descriptors.add(element); + } + + return element; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/binaryxml/BinaryXMLDescriber.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/binaryxml/BinaryXMLDescriber.java index 846eb44..ba78565 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/binaryxml/BinaryXMLDescriber.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/binaryxml/BinaryXMLDescriber.java @@ -16,6 +16,9 @@ package com.android.ide.eclipse.adt.internal.editors.binaryxml; +import com.android.ide.eclipse.adt.AdtPlugin; + +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.content.IContentDescriber; import org.eclipse.core.runtime.content.IContentDescription; @@ -50,20 +53,28 @@ public class BinaryXMLDescriber implements IContentDescriber { * InputStream, org.eclipse.core.runtime.content.IContentDescription) */ public int describe(InputStream contents, IContentDescription description) throws IOException { + int status = INVALID; int length = 8; byte[] bytes = new byte[length]; - if (contents.read(bytes, 0, length) != length) { - return INVALID; + if (contents.read(bytes, 0, length) == length) { + ByteBuffer buf = ByteBuffer.wrap(bytes); + buf.order(ByteOrder.LITTLE_ENDIAN); + short type = buf.getShort(); + short headerSize = buf.getShort(); + int size = buf.getInt(); // chunk size + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.ERROR, "BinaryXML: type 0x%04x, headerSize 0x%04x, size 0x%08x", type, headerSize, size); + } + if (type == RES_XML_TYPE && headerSize == RES_XML_HEADER_SIZE) { + status = VALID; + } } - ByteBuffer buf = ByteBuffer.wrap(bytes); - buf.order(ByteOrder.LITTLE_ENDIAN); - short type = buf.getShort(); - short headerSize = buf.getShort(); - buf.getInt(); // chunk size - if (type == RES_XML_TYPE && headerSize == RES_XML_HEADER_SIZE) { - return VALID; + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.ERROR, "BinaryXML status: %d (%s)", + status, + status == VALID ? "VALID" : "INVALID"); } - return INVALID; + return status; } /* diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorContentAssist.java new file mode 100644 index 0000000..1570439 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorContentAssist.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.color; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; + +/** + * Content Assist Processor for /res/color XML files + */ +@VisibleForTesting +public final class ColorContentAssist extends AndroidContentAssist { + public ColorContentAssist() { + super(AndroidTargetData.DESCRIPTOR_COLOR); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorDescriptors.java new file mode 100644 index 0000000..f6e50a8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorDescriptors.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.color; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; +import static com.android.sdklib.SdkConstants.NS_RESOURCES; + +import com.android.ide.common.api.IAttributeInfo.Format; +import com.android.ide.common.resources.platform.AttributeInfo; +import com.android.ide.common.resources.platform.DeclareStyleableInfo; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.resources.ResourceType; +import com.android.sdklib.SdkConstants; + +import java.util.Map; + +/** Descriptors for /res/color XML files */ +public class ColorDescriptors implements IDescriptorProvider { + private static final String SDK_URL = + "http://d.android.com/guide/topics/resources/color-list-resource.html"; //$NON-NLS-1$ + + /** The root element descriptor */ + private ElementDescriptor mDescriptor = new ElementDescriptor( + "selector", "Selector", + "Required. This must be the root element. Contains one or more <item> elements.", + SDK_URL, + new AttributeDescriptor[] { new XmlnsAttributeDescriptor(ANDROID_NS_NAME, + NS_RESOURCES) }, + null /*children: added later*/, true /*mandatory*/); + + /** @return the root descriptor. */ + public ElementDescriptor getDescriptor() { + if (mDescriptor == null) { + mDescriptor = new ElementDescriptor("", getRootElementDescriptors()); //$NON-NLS-1$ + } + + return mDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return new ElementDescriptor[] { mDescriptor }; + } + + public synchronized void updateDescriptors(Map<String, DeclareStyleableInfo> styleMap) { + if (styleMap == null) { + return; + } + + // Selector children + ElementDescriptor selectorItem = AnimatorDescriptors.addElement(null, styleMap, + "item", "Item", "DrawableStates", null, //$NON-NLS-1$ //$NON-NLS-3$ + "Defines a drawable to use during certain states, as described by " + + "its attributes. Must be a child of a <selector> element.", + SDK_URL, + new ReferenceAttributeDescriptor( + ResourceType.COLOR, "color", "color", //$NON-NLS-1$ //$NON-NLS-2$ + SdkConstants.NS_RESOURCES, + "Hexadeximal color. Required. The color is specified with an RGB value and " + + "optional alpha channel.\n" + + "The value always begins with a pound (#) character and then " + + "followed by the Alpha-Red-Green-Blue information in one of " + + "the following formats:\n" + + "* RGB\n" + + "* ARGB\n" + + "* RRGGBB\n" + + "* AARRGGBB", + new AttributeInfo("drawable", new Format[] { Format.COLOR })), + null, /* This is wrong -- we can now embed any above drawable + (but without xmlns as extra) */ + false /*mandatory*/); + + if (selectorItem != null) { + mDescriptor.setChildren(new ElementDescriptor[] { selectorItem }); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorEditor.java new file mode 100644 index 0000000..b0e3327 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorEditor.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.color; + +import static com.android.ide.eclipse.adt.AdtConstants.EDITORS_NAMESPACE; + +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; + +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Editor for /res/color XML files. + */ +@SuppressWarnings("restriction") +public class ColorEditor extends AndroidXmlEditor { + public static final String ID = EDITORS_NAMESPACE + ".color.ColorEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiRootNode; + + public ColorEditor() { + super(); + } + + @Override + public UiElementNode getUiRootNode() { + return mUiRootNode; + } + + @Override + public boolean isSaveAsAllowed() { + return true; + } + + @Override + protected void createFormPages() { + /* Disabled for now; doesn't work quite right + try { + addPage(new ColorTreePage(this)); + } catch (PartInitException e) { + AdtPlugin.log(IStatus.ERROR, "Error creating nested page"); //$NON-NLS-1$ + AdtPlugin.getDefault().getLog().log(e.getStatus()); + } + */ + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", + file.getName())); + } + } + + @Override + protected void xmlModelChanged(Document xmlDoc) { + // create the ui root node on demand. + initUiRootNode(false /*force*/); + + Element rootElement = xmlDoc.getDocumentElement(); + mUiRootNode.loadFromXmlNode(rootElement); + + super.xmlModelChanged(xmlDoc); + } + + @Override + protected void initUiRootNode(boolean force) { + // The manifest UI node is always created, even if there's no corresponding XML node. + if (mUiRootNode == null || force) { + ElementDescriptor descriptor; + AndroidTargetData data = getTargetData(); + if (data == null) { + descriptor = new ColorDescriptors().getDescriptor(); + } else { + descriptor = data.getColorDescriptors().getDescriptor(); + } + mUiRootNode = descriptor.createUiNode(); + mUiRootNode.setEditor(this); + onDescriptorsChanged(); + } + } + + private void onDescriptorsChanged() { + IStructuredModel model = getModelForRead(); + if (model != null) { + try { + Node node = getXmlDocument(model).getDocumentElement(); + mUiRootNode.reloadFromXmlNode(node); + } finally { + model.releaseFromRead(); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorSourceViewerConfig.java new file mode 100644 index 0000000..4f12ce5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/color/ColorSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.color; + + +import com.android.ide.eclipse.adt.internal.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration that calls in ColorContentAssist. + */ +public class ColorSourceViewerConfig extends AndroidSourceViewerConfig { + + public ColorSourceViewerConfig() { + super(new ColorContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java index 222684d..3e4c6d0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/AttributeDescriptor.java @@ -17,7 +17,6 @@ package com.android.ide.eclipse.adt.internal.editors.descriptors; import com.android.ide.common.api.IAttributeInfo; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; @@ -35,7 +34,9 @@ import org.eclipse.swt.graphics.Image; * This is an abstract class. Derived classes must implement data description and return * the correct UiAttributeNode-derived class. */ -public abstract class AttributeDescriptor { +public abstract class AttributeDescriptor implements Comparable<AttributeDescriptor> { + public static final String ATTRIBUTE_ICON_FILENAME = "attribute"; //$NON-NLS-1$ + private final String mXmlLocalName; private final String mNsUri; private final IAttributeInfo mAttrInfo; @@ -53,6 +54,7 @@ public abstract class AttributeDescriptor { * or attribute separator, all of which do not represent any real attribute.) */ public AttributeDescriptor(String xmlLocalName, String nsUri, IAttributeInfo attrInfo) { + assert xmlLocalName != null; mXmlLocalName = xmlLocalName; mNsUri = nsUri; mAttrInfo = attrInfo; @@ -95,18 +97,13 @@ public abstract class AttributeDescriptor { /** * Returns an optional icon for the attribute. - * <p/> - * By default this tries to return an icon based on the XML name of the attribute. - * If this fails, it tries to return the default Android logo as defined in the - * plugin. If all fails, it returns null. + * This icon is generic, that is all attribute descriptors have the same icon + * no matter what they represent. * * @return An icon for this element or null. */ - public Image getIcon() { - IconFactory factory = IconFactory.getInstance(); - Image icon; - icon = factory.getIcon(getXmlLocalName(), IconFactory.COLOR_RED, IconFactory.SHAPE_CIRCLE); - return icon != null ? icon : AdtPlugin.getAndroidLogo(); + public Image getGenericIcon() { + return IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME); } /** @@ -115,4 +112,9 @@ public abstract class AttributeDescriptor { * attribute has no user interface. */ public abstract UiAttributeNode createUiNode(UiElementNode uiParent); + + // Implements Comparable<AttributeDescriptor> + public int compareTo(AttributeDescriptor other) { + return mXmlLocalName.compareTo(other.mXmlLocalName); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java index d0806c5..f1def39 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/BooleanAttributeDescriptor.java @@ -24,11 +24,11 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiListAttributeNode; * It is displayed by a {@link UiListAttributeNode}. */ public class BooleanAttributeDescriptor extends ListAttributeDescriptor { + private static final String[] VALUES = new String[] { "true", "false" }; //$NON-NLS-1$ //$NON-NLS-2$ public BooleanAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, String tooltip, IAttributeInfo attrInfo) { - super(xmlLocalName, uiName, nsUri, tooltip, attrInfo, - new String[] { "true", "false" }); //$NON-NLS-1$ //$NON-NLS-2$ + super(xmlLocalName, uiName, nsUri, tooltip, attrInfo, VALUES); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java index 817d4cb..c7bc5a1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtils.java @@ -21,6 +21,7 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_BELOW; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.common.layout.LayoutConstants.EDIT_TEXT; import static com.android.ide.common.layout.LayoutConstants.EXPANDABLE_LIST_VIEW; import static com.android.ide.common.layout.LayoutConstants.FQCN_ADAPTER_VIEW; import static com.android.ide.common.layout.LayoutConstants.GALLERY; @@ -31,10 +32,11 @@ import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.RELATIVE_LAYOUT; import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.REQUEST_FOCUS; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.resources.platform.AttributeInfo; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.resources.ResourceType; @@ -64,7 +66,7 @@ public final class DescriptorsUtils { * The path in the online documentation for the manifest description. * <p/> * This is NOT a complete URL. To be used, it needs to be appended - * to {@link AndroidConstants#CODESITE_BASE_URL} or to the local SDK + * to {@link AdtConstants#CODESITE_BASE_URL} or to the local SDK * documentation. */ public static final String MANIFEST_SDK_URL = "/reference/android/R.styleable.html#"; //$NON-NLS-1$ @@ -357,12 +359,12 @@ public final class DescriptorsUtils { * @return The capitalized string */ public static String capitalize(String str) { - if (str == null || str.length() < 1 || str.charAt(0) < 'a' || str.charAt(0) > 'z') { + if (str == null || str.length() < 1 || Character.isUpperCase(str.charAt(0))) { return str; } StringBuilder sb = new StringBuilder(); - sb.append((char)(str.charAt(0) + 'A' - 'a')); + sb.append(Character.toUpperCase(str.charAt(0))); sb.append(str.substring(1)); return sb.toString(); } @@ -458,7 +460,7 @@ public final class DescriptorsUtils { StringBuilder sb = new StringBuilder(); - Image icon = elementDescriptor.getIcon(); + Image icon = elementDescriptor.getCustomizedIcon(); if (icon != null) { sb.append("<form><li style=\"image\" value=\"" + //$NON-NLS-1$ IMAGE_KEY + "\">"); //$NON-NLS-1$ @@ -670,6 +672,23 @@ public final class DescriptorsUtils { } /** + * Returns the basename for the given fully qualified class name. It is okay to pass + * a basename to this method which will just be returned back. + * + * @param fqcn The fully qualified class name to convert + * @return the basename of the class name + */ + public static String getBasename(String fqcn) { + String name = fqcn; + int lastDot = name.lastIndexOf('.'); + if (lastDot != -1) { + name = name.substring(lastDot + 1); + } + + return name; + } + + /** * Sets the default layout attributes for the a new UiElementNode. * <p/> * Note that ideally the node should already be part of a hierarchy so that its @@ -677,49 +696,59 @@ public final class DescriptorsUtils { * <p/> * This does not override attributes which are not empty. */ - public static void setDefaultLayoutAttributes(UiElementNode ui_node, boolean updateLayout) { + public static void setDefaultLayoutAttributes(UiElementNode node, boolean updateLayout) { // if this ui_node is a layout and we're adding it to a document, use match_parent for // both W/H. Otherwise default to wrap_layout. - boolean fill = ui_node.getDescriptor().hasChildren() && - ui_node.getUiParent() instanceof UiDocumentNode; - ui_node.setAttributeValue( + ElementDescriptor descriptor = node.getDescriptor(); + + if (descriptor.getXmlLocalName().equals(REQUEST_FOCUS)) { + // Don't add ids etc to <requestFocus> + return; + } + + boolean fill = descriptor.hasChildren() && + node.getUiParent() instanceof UiDocumentNode; + node.setAttributeValue( ATTR_LAYOUT_WIDTH, SdkConstants.NS_RESOURCES, fill ? VALUE_FILL_PARENT : VALUE_WRAP_CONTENT, false /* override */); - ui_node.setAttributeValue( + node.setAttributeValue( ATTR_LAYOUT_HEIGHT, SdkConstants.NS_RESOURCES, fill ? VALUE_FILL_PARENT : VALUE_WRAP_CONTENT, false /* override */); - String widget_id = getFreeWidgetId(ui_node); - if (widget_id != null) { - ui_node.setAttributeValue( + String freeId = getFreeWidgetId(node); + if (freeId != null) { + node.setAttributeValue( ATTR_ID, SdkConstants.NS_RESOURCES, - widget_id, + freeId, false /* override */); } - String widget_type = ui_node.getDescriptor().getUiName(); - ui_node.setAttributeValue( + // Don't set default text value into edit texts - they typically start out blank + if (!descriptor.getXmlLocalName().equals(EDIT_TEXT)) { + String type = getBasename(descriptor.getUiName()); + node.setAttributeValue( ATTR_TEXT, SdkConstants.NS_RESOURCES, - widget_type, + type, false /*override*/); + } if (updateLayout) { - UiElementNode ui_parent = ui_node.getUiParent(); - if (ui_parent != null && - ui_parent.getDescriptor().getXmlLocalName().equals( + UiElementNode parent = node.getUiParent(); + if (parent != null && + parent.getDescriptor().getXmlLocalName().equals( RELATIVE_LAYOUT)) { - UiElementNode ui_previous = ui_node.getUiPreviousSibling(); - if (ui_previous != null) { - String id = ui_previous.getAttributeValue(ATTR_ID); + UiElementNode previous = node.getUiPreviousSibling(); + if (previous != null) { + String id = previous.getAttributeValue(ATTR_ID); if (id != null && id.length() > 0) { id = id.replace("@+", "@"); //$NON-NLS-1$ //$NON-NLS-2$ - ui_node.setAttributeValue( + node.setAttributeValue( ATTR_LAYOUT_BELOW, SdkConstants.NS_RESOURCES, id, @@ -740,7 +769,7 @@ public final class DescriptorsUtils { * (e.g. "@+id/something") */ public static String getFreeWidgetId(UiElementNode uiNode) { - String name = uiNode.getDescriptor().getXmlLocalName(); + String name = getBasename(uiNode.getDescriptor().getXmlLocalName()); return getFreeWidgetId(uiNode.getUiRoot(), name); } @@ -891,33 +920,4 @@ public final class DescriptorsUtils { return false; } - - /** - * Converts the given attribute value to an XML-attribute-safe value, meaning that - * single and double quotes are replaced with their corresponding XML entities. - * - * @param attrValue the value to be escaped - * @return the escaped value - */ - public static String toXmlAttributeValue(String attrValue) { - // Must escape ' and " - if (attrValue.indexOf('"') == -1 && attrValue.indexOf('\'') == -1) { - return attrValue; - } - - int n = attrValue.length(); - StringBuilder sb = new StringBuilder(2 * n); - for (int i = 0; i < n; i++) { - char c = attrValue.charAt(i); - if (c == '"') { - sb.append("""); //$NON-NLS-1$ - } else if (c == '\'') { - sb.append("'"); //$NON-NLS-1$ - } else { - sb.append(c); - } - } - - return sb.toString(); - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java index 316f020..8ab25cb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java @@ -16,6 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.descriptors; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; + import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; @@ -39,8 +41,10 @@ import java.util.Set; * and it will cease to exist when the XML node ceases to exist. */ public class ElementDescriptor implements Comparable<ElementDescriptor> { + private static final String ELEMENT_ICON_FILENAME = "element"; //$NON-NLS-1$ + /** The XML element node name. Case sensitive. */ - private final String mXmlName; + protected final String mXmlName; /** The XML element name for the user interface, typically capitalized. */ private final String mUiName; /** The list of allowed attributes. */ @@ -208,7 +212,7 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { */ public final String getNamespace() { // For now we hard-code the prefix as being "android" - if (mXmlName.startsWith("android:")) { //$NON-NLs-1$ + if (mXmlName.startsWith(ANDROID_NS_NAME_PREFIX)) { return SdkConstants.NS_RESOURCES; } @@ -222,27 +226,52 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> { } /** - * Returns an optional icon for the element. + * Returns an icon for the element. + * This icon is generic, that is all element descriptors have the same icon + * no matter what they represent. + * + * @return An icon for this element or null. + * @see #getCustomizedIcon() + */ + public Image getGenericIcon() { + return IconFactory.getInstance().getIcon(ELEMENT_ICON_FILENAME); + } + + /** + * Returns an optional icon for the element, typically to be used in XML form trees. + * <p/> + * This icon is customized to the given descriptor, that is different elements + * will have different icons. * <p/> * By default this tries to return an icon based on the XML name of the element. * If this fails, it tries to return the default Android logo as defined in the * plugin. If all fails, it returns null. * - * @return An icon for this element or null. + * @return An icon for this element. This is never null. */ - public Image getIcon() { + public Image getCustomizedIcon() { IconFactory factory = IconFactory.getInstance(); - int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; - int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; + int color = hasChildren() ? IconFactory.COLOR_BLUE + : IconFactory.COLOR_GREEN; + int shape = hasChildren() ? IconFactory.SHAPE_RECT + : IconFactory.SHAPE_CIRCLE; String name = mXmlName; - if (name.indexOf('.') != -1) { + + int pos = name.lastIndexOf('.'); + if (pos != -1) { // If the user uses a fully qualified name, such as - // "android.gesture.GestureOverlayView" in their XML, we need to look up - // only by basename - name = name.substring(name.lastIndexOf('.') + 1); + // "android.gesture.GestureOverlayView" in their XML, we need to + // look up only by basename + name = name.substring(pos + 1); } Image icon = factory.getIcon(name, color, shape); - return icon != null ? icon : AdtPlugin.getAndroidLogo(); + if (icon == null) { + icon = getGenericIcon(); + } + if (icon == null) { + icon = AdtPlugin.getAndroidLogo(); + } + return icon; } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableContentAssist.java new file mode 100644 index 0000000..b0bea51 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableContentAssist.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.drawable; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; + +/** + * Content Assist Processor for /res/drawable XML files + */ +@VisibleForTesting +public final class DrawableContentAssist extends AndroidContentAssist { + public DrawableContentAssist() { + super(AndroidTargetData.DESCRIPTOR_DRAWABLE); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableDescriptors.java new file mode 100644 index 0000000..1e71795 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableDescriptors.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.drawable; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; + +import com.android.ide.common.api.IAttributeInfo.Format; +import com.android.ide.common.resources.platform.AttributeInfo; +import com.android.ide.common.resources.platform.DeclareStyleableInfo; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.resources.ResourceType; +import com.android.sdklib.SdkConstants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Descriptors for /res/drawable files + */ +public class DrawableDescriptors implements IDescriptorProvider { + private static final String SDK_URL_BASE = + "http://d.android.com/guide/topics/resources/"; //$NON-NLS-1$ + + /** The root element descriptor */ + private ElementDescriptor mDescriptor; + /** The root element descriptors */ + private ElementDescriptor[] mRootDescriptors; + private Map<String, ElementDescriptor> nameToDescriptor; + + /** @return the root descriptor. */ + public ElementDescriptor getDescriptor() { + if (mDescriptor == null) { + mDescriptor = new ElementDescriptor("", getRootElementDescriptors()); //$NON-NLS-1$ + } + + return mDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mRootDescriptors; + } + + /** + * Returns a descriptor for the given root tag name + * + * @param tag the tag name to look up a descriptor for + * @return a descriptor with the given tag name + */ + public ElementDescriptor getElementDescriptor(String tag) { + if (nameToDescriptor == null) { + nameToDescriptor = new HashMap<String, ElementDescriptor>(); + for (ElementDescriptor descriptor : getRootElementDescriptors()) { + nameToDescriptor.put(descriptor.getXmlName(), descriptor); + } + } + + ElementDescriptor descriptor = nameToDescriptor.get(tag); + if (descriptor == null) { + descriptor = getDescriptor(); + } + return descriptor; + } + + public synchronized void updateDescriptors(Map<String, DeclareStyleableInfo> styleMap) { + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_NAME, + SdkConstants.NS_RESOURCES); + Format[] referenceFormat = new Format[] { Format.REFERENCE }; + + List<ElementDescriptor> descriptors = new ArrayList<ElementDescriptor>(); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "animation-list", "Animation List", "AnimationDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An animation defined in XML that shows a sequence of images in " + + "order (like a film)", + SDK_URL_BASE + "animation-resource.html#Frame", + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "animated-rotate", "Animated Rotate", "AnimatedRotateDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + // Need docs + null /* tooltip */, + null /* sdk_url */, + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "bitmap", "BitMap", "BitmapDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML bitmap is a resource defined in XML that points to a bitmap file. " + + "The effect is an alias for a raw bitmap file. The XML can " + + "specify additional properties for the bitmap such as " + + "dithering and tiling.", + SDK_URL_BASE + "drawable-resource.html#Bitmap", //$NON-NLS-1$ + xmlns, null, true /* mandatory */); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "clip", "Clip", "ClipDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML file that defines a drawable that clips another Drawable based on " + + "this Drawable's current level value.", + SDK_URL_BASE + "drawable-resource.html#Clip", //$NON-NLS-1$ + xmlns, null, true /*mandatory*/); + + + AnimatorDescriptors.addElement(descriptors, styleMap, + "color", "Color", "ColorDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "XML resource that carries a color value (a hexadecimal color)", + SDK_URL_BASE + "more-resources.html#Color", + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "inset", "Inset", "InsetDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML file that defines a drawable that insets another drawable by a " + + "specified distance. This is useful when a View needs a background " + + "drawble that is smaller than the View's actual bounds.", + SDK_URL_BASE + "drawable-resource.html#Inset", //$NON-NLS-1$ + xmlns, null, true /*mandatory*/); + + // Layer list + + // An <item> in a <selector> or < + ElementDescriptor layerItem = AnimatorDescriptors.addElement(null, styleMap, + "item", "Item", "LayerDrawableItem", null, //$NON-NLS-1$ //$NON-NLS-3$ + "Defines a drawable to place in the layer drawable, in a position " + + "defined by its attributes. Must be a child of a <selector> " + + "element. Accepts child <bitmap> elements.", + SDK_URL_BASE + "drawable-resource.html#LayerList", //$NON-NLS-1$ + null, /* extra attribute */ + null, /* This is wrong -- we can now embed any above drawable + (but without xmlns as extra) */ + false /*mandatory*/); + ElementDescriptor[] layerChildren = layerItem != null + ? new ElementDescriptor[] { layerItem } : null; + + AnimatorDescriptors.addElement(descriptors, styleMap, + "layer-list", "Layer List", "LayerDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "A Drawable that manages an array of other Drawables. These are drawn in " + + "array order, so the element with the largest index is be drawn on top.", + SDK_URL_BASE + "drawable-resource.html#LayerList", //$NON-NLS-1$ + xmlns, + layerChildren, + true /*mandatory*/); + + // Level list children + ElementDescriptor levelListItem = AnimatorDescriptors.addElement(null, styleMap, + "item", "Item", "LevelListDrawableItem", null, //$NON-NLS-1$ //$NON-NLS-3$ + "Defines a drawable to use at a certain level.", + SDK_URL_BASE + "drawable-resource.html#LevelList", //$NON-NLS-1$ + null, /* extra attribute */ + null, /* no further children */ + // TODO: The inflation code seems to show that all drawables can be nested here! + false /*mandatory*/); + AnimatorDescriptors.addElement(descriptors, styleMap, + "level-list", "Level List", "LevelListDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML file that defines a drawable that manages a number of alternate " + + "Drawables, each assigned a maximum numerical value", + SDK_URL_BASE + "drawable-resource.html#LevelList", //$NON-NLS-1$ + xmlns, + levelListItem != null ? new ElementDescriptor[] { levelListItem } : null, + true /*mandatory*/); + + // Not yet supported + //addElement(descriptors, styleMap, "mipmap", "Mipmap", "MipmapDrawable", null, + // null /* tooltip */, + // null /* sdk_url */, + // xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "nine-patch", "Nine Patch", "NinePatchDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "A PNG file with stretchable regions to allow image resizing " + + "based on content (.9.png).", + SDK_URL_BASE + "drawable-resource.html#NinePatch", //$NON-NLS-1$ + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "rotate", "Rotate", "RotateDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + // Need docs + null /* tooltip */, + null /* sdk_url */, + xmlns, null, true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "scale", "Shape", "ScaleDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML file that defines a drawable that changes the size of another Drawable " + + "based on its current level value.", + SDK_URL_BASE + "drawable-resource.html#Scale", //$NON-NLS-1$ + xmlns, null, true /*mandatory*/); + + // Selector children + ElementDescriptor selectorItem = AnimatorDescriptors.addElement(null, styleMap, + "item", "Item", "DrawableStates", null, //$NON-NLS-1$ //$NON-NLS-3$ + "Defines a drawable to use during certain states, as described by " + + "its attributes. Must be a child of a <selector> element.", + SDK_URL_BASE + "drawable-resource.html#StateList", //$NON-NLS-1$ + new ReferenceAttributeDescriptor( + ResourceType.DRAWABLE, "drawable", "drawable", //$NON-NLS-1$ //$NON-NLS-2$ + SdkConstants.NS_RESOURCES, + "Reference to a drawable resource.", + new AttributeInfo("drawable", referenceFormat)), + null, /* This is wrong -- we can now embed any above drawable + (but without xmlns as extra) */ + false /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "selector", "Selector", "StateListDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML file that references different bitmap graphics for different states " + + "(for example, to use a different image when a button is pressed).", + SDK_URL_BASE + "drawable-resource.html#StateList", //$NON-NLS-1$ + xmlns, + selectorItem != null ? new ElementDescriptor[] { selectorItem } : null, + true /*mandatory*/); + + // Shape + // Shape children + List<ElementDescriptor> shapeChildren = new ArrayList<ElementDescriptor>(); + // Selector children + AnimatorDescriptors.addElement(shapeChildren, styleMap, + "size", "Size", "GradientDrawableSize", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, null /* sdk_url */, null /* extra attribute */, + null /* children */, false /* mandatory */); + AnimatorDescriptors.addElement(shapeChildren, styleMap, + "gradient", "Gradient", "GradientDrawableGradient", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, null /* sdk_url */, null /* extra attribute */, + null /* children */, false /* mandatory */); + AnimatorDescriptors.addElement(shapeChildren, styleMap, + "solid", "Solid", "GradientDrawableSolid", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, null /* sdk_url */, null /* extra attribute */, + null /* children */, false /* mandatory */); + AnimatorDescriptors.addElement(shapeChildren, styleMap, + "stroke", "Stroke", "GradientDrawableStroke", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, null /* sdk_url */, null /* extra attribute */, + null /* children */, false /* mandatory */); + AnimatorDescriptors.addElement(shapeChildren, styleMap, + "corners", "Corners", "DrawableCorners", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, null /* sdk_url */, null /* extra attribute */, + null /* children */, false /* mandatory */); + AnimatorDescriptors.addElement(shapeChildren, styleMap, + "padding", "Padding", "GradientDrawablePadding", null, //$NON-NLS-1$ //$NON-NLS-3$ + null /* tooltip */, null /* sdk_url */, null /* extra attribute */, + null /* children */, false /* mandatory */); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "shape", "Shape", //$NON-NLS-1$ + + // The documentation says that a <shape> element creates a ShapeDrawable, + // but ShapeDrawable isn't finished and the code currently creates + // a GradientDrawable. + //"ShapeDrawable", //$NON-NLS-1$ + "GradientDrawable", //$NON-NLS-1$ + + null, + "An XML file that defines a geometric shape, including colors and gradients.", + SDK_URL_BASE + "drawable-resource.html#Shape", //$NON-NLS-1$ + xmlns, + + // These are the GradientDrawable children, not the ShapeDrawable children + shapeChildren.toArray(new ElementDescriptor[shapeChildren.size()]), + true /*mandatory*/); + + AnimatorDescriptors.addElement(descriptors, styleMap, + "transition", "Transition", "TransitionDrawable", null, //$NON-NLS-1$ //$NON-NLS-3$ + "An XML file that defines a drawable that can cross-fade between two " + + "drawable resources. Each drawable is represented by an <item> " + + "element inside a single <transition> element. No more than two " + + "items are supported. To transition forward, call startTransition(). " + + "To transition backward, call reverseTransition().", + SDK_URL_BASE + "drawable-resource.html#Transition", //$NON-NLS-1$ + xmlns, + layerChildren, // children: a TransitionDrawable is a LayerDrawable + true /*mandatory*/); + + mRootDescriptors = descriptors.toArray(new ElementDescriptor[descriptors.size()]); + + // A <selector><item> can contain any of the top level drawables + if (selectorItem != null) { + selectorItem.setChildren(mRootDescriptors); + } + // Docs says it can accept <bitmap> but code comment suggests any is possible; + // test and either use this or just { bitmap } + if (layerItem != null) { + layerItem.setChildren(mRootDescriptors); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableEditor.java new file mode 100644 index 0000000..aaaeb4b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableEditor.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.drawable; + +import static com.android.ide.eclipse.adt.AdtConstants.EDITORS_NAMESPACE; + +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; + +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Editor for /res/drawable XML files. + */ +@SuppressWarnings("restriction") +public class DrawableEditor extends AndroidXmlEditor { + public static final String ID = EDITORS_NAMESPACE + ".drawable.DrawableEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiRootNode; + /** The tag used at the root */ + private String mRootTag; + + /** + * Creates the form editor for resources XML files. + */ + public DrawableEditor() { + super(); + } + + @Override + public UiElementNode getUiRootNode() { + return mUiRootNode; + } + + @Override + public boolean isSaveAsAllowed() { + return true; + } + + @Override + protected void createFormPages() { + /* Disabled for now; doesn't work quite right + try { + addPage(new DrawableTreePage(this)); + } catch (PartInitException e) { + AdtPlugin.log(IStatus.ERROR, "Error creating nested page"); //$NON-NLS-1$ + AdtPlugin.getDefault().getLog().log(e.getStatus()); + } + */ + } + + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", + file.getName())); + } + } + + @Override + protected void xmlModelChanged(Document xmlDoc) { + Element rootElement = xmlDoc.getDocumentElement(); + if (rootElement != null) { + mRootTag = rootElement.getTagName(); + } + + initUiRootNode(false /*force*/); + + if (mRootTag != null + && !mRootTag.equals(mUiRootNode.getDescriptor().getXmlLocalName())) { + AndroidTargetData data = getTargetData(); + if (data != null) { + ElementDescriptor descriptor = + data.getDrawableDescriptors().getElementDescriptor(mRootTag); + // Replace top level node now that we know the actual type + + // Disconnect from old + mUiRootNode.setEditor(null); + mUiRootNode.setXmlDocument(null); + + // Create new + mUiRootNode = descriptor.createUiNode(); + mUiRootNode.setXmlDocument(xmlDoc); + mUiRootNode.setEditor(this); + } + } + + if (mUiRootNode.getDescriptor() instanceof DocumentDescriptor) { + mUiRootNode.loadFromXmlNode(xmlDoc); + } else { + mUiRootNode.loadFromXmlNode(rootElement); + } + + super.xmlModelChanged(xmlDoc); + } + + @Override + protected void initUiRootNode(boolean force) { + // The manifest UI node is always created, even if there's no corresponding XML node. + if (mUiRootNode == null || force) { + ElementDescriptor descriptor; + boolean reload = false; + AndroidTargetData data = getTargetData(); + if (data == null) { + descriptor = new DocumentDescriptor("temp", null /*children*/); + } else { + descriptor = data.getDrawableDescriptors().getElementDescriptor(mRootTag); + reload = true; + } + mUiRootNode = descriptor.createUiNode(); + mUiRootNode.setEditor(this); + + if (reload) { + onDescriptorsChanged(); + } + } + } + + private void onDescriptorsChanged() { + IStructuredModel model = getModelForRead(); + if (model != null) { + try { + Node node = getXmlDocument(model).getDocumentElement(); + mUiRootNode.reloadFromXmlNode(node); + } finally { + model.releaseFromRead(); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableSourceViewerConfig.java new file mode 100644 index 0000000..eef6a9e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/drawable/DrawableSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.drawable; + + +import com.android.ide.eclipse.adt.internal.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration for drawable files + */ +public class DrawableSourceViewerConfig extends AndroidSourceViewerConfig { + + public DrawableSourceViewerConfig() { + super(new DrawableContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/AbstractPropertiesFieldsPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/AbstractPropertiesFieldsPart.java index 06169d2..0d72614 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/AbstractPropertiesFieldsPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/AbstractPropertiesFieldsPart.java @@ -34,6 +34,7 @@ import org.eclipse.ui.forms.widgets.Section; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; /** * Section part for editing fields of a properties file in an Export editor. @@ -302,13 +303,15 @@ abstract class AbstractPropertiesFieldsPart extends ManifestSectionPart { } // Clear the text of any keyword we didn't find in the document - for (String key : allKeywords) { + Iterator<String> iterator = allKeywords.iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); Control field = mNameToField.get(key); if (field != null) { try { mInternalTextUpdate = true; setFieldText(field, ""); - allKeywords.remove(key); + iterator.remove(); } finally { mInternalTextUpdate = false; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/ExportEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/ExportEditor.java index 50d9fd8..769f74e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/ExportEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/export/ExportEditor.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors.export; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidTextEditor; import org.eclipse.core.resources.IFile; @@ -32,7 +32,7 @@ import org.eclipse.ui.part.FileEditorInput; */ public class ExportEditor extends AndroidTextEditor { - public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".text.ExportEditor"; //$NON-NLS-1$ + public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".text.ExportEditor"; //$NON-NLS-1$ private ExportPropertiesPage mExportPropsPage; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java index d8911e4..7c40097 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java @@ -22,6 +22,8 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.layoutlib.api.IXmlPullParser; import com.android.sdklib.SdkConstants; import org.kxml2.io.KXmlParser; @@ -35,23 +37,22 @@ import org.kxml2.io.KXmlParser; */ public class ContextPullParser extends KXmlParser implements ILayoutPullParser { - private final String mName; - private final ILayoutPullParser mEmbeddedParser; + private final IProjectCallback mProjectCallback; - public ContextPullParser(String name, ILayoutPullParser embeddedParser) { + public ContextPullParser(IProjectCallback projectCallback) { super(); - mName = name; - mEmbeddedParser = embeddedParser; + mProjectCallback = projectCallback; } // --- Layout lib API methods + /** + * this is deprecated but must still be implemented for older layout libraries. + * @deprecated use {@link IProjectCallback#getParser(String)}. + */ + @Deprecated public ILayoutPullParser getParser(String layoutName) { - if (mName.equals(layoutName)) { - return mEmbeddedParser; - } - - return null; + return mProjectCallback.getParser(layoutName); } public Object getViewCookie() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java index 4b313f6..d55db31 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java @@ -16,16 +16,18 @@ package com.android.ide.eclipse.adt.internal.editors.layout; +import com.android.annotations.VisibleForTesting; import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; /** * Content Assist Processor for /res/layout XML files */ -class LayoutContentAssist extends AndroidContentAssist { +@VisibleForTesting +public final class LayoutContentAssist extends AndroidContentAssist { /** - * Constructor for LayoutContentAssist + * Constructor for LayoutContentAssist */ public LayoutContentAssist() { super(AndroidTargetData.DESCRIPTOR_LAYOUT); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java index 70e062a..bccc7d0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; @@ -25,6 +25,7 @@ import com.android.ide.eclipse.adt.internal.editors.descriptors.IUnknownDescript import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.OutlinePage; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PropertySheetPage; @@ -40,6 +41,7 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.text.edits.TextEdit; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; @@ -64,11 +66,14 @@ import java.util.Set; */ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput, IPartListener { - public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".layout.LayoutEditor"; //$NON-NLS-1$ + public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".layout.LayoutEditor"; //$NON-NLS-1$ /** Root node of the UI element hierarchy */ private UiDocumentNode mUiRootNode; + /** Flag used to ignore XML model updates */ + private boolean mIgnoreXmlUpdate; + private GraphicalEditorPart mGraphicalEditor; private int mGraphicalEditorIndex; /** Implementation of the {@link IContentOutlinePage} for this editor */ @@ -285,12 +290,45 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput, } /** + * Controls whether XML models are ignored or not. Can be used in conjunction with the + * {@link #refreshXmlModel()} method to suppress many smaller individual edits and + * then flush a large model update at the end. This is for example used for + * refactoring, such that we don't update the model as each individual + * {@link TextEdit} is applied, and instead we process them all in a single refresh at + * the end. + * + * @param ignore when true, ignore all subsequent XML model updates, when false start + * processing XML model updates again + */ + public void setIgnoreXmlUpdate(boolean ignore) { + mIgnoreXmlUpdate = ignore; + } + + /** Performs a complete refresh of the XML model */ + public void refreshXmlModel() { + Document xmlDoc = mUiRootNode.getXmlDocument(); + + initUiRootNode(true /*force*/); + mUiRootNode.loadFromXmlNode(xmlDoc); + // update the model first, since it is used by the viewers. + super.xmlModelChanged(xmlDoc); + + if (mGraphicalEditor != null) { + mGraphicalEditor.onXmlModelChanged(); + } + } + + /** * Processes the new XML Model, which XML root node is given. * * @param xml_doc The XML document, if available, or null if none exists. */ @Override protected void xmlModelChanged(Document xml_doc) { + if (mIgnoreXmlUpdate) { + return; + } + // init the ui root on demand initUiRootNode(false /*force*/); @@ -351,7 +389,7 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput, ISourceViewer textViewer = getStructuredSourceViewer(); int caretOffset = textViewer.getTextWidget().getCaretOffset(); if (caretOffset >= 0) { - Node node = AndroidXmlEditor.getNode(textViewer.getDocument(), caretOffset); + Node node = DomUtilities.getNode(textViewer.getDocument(), caretOffset); if (node != null && mGraphicalEditor != null) { mGraphicalEditor.select(node); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java index 380381b..ba77ce9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java @@ -16,11 +16,11 @@ package com.android.ide.eclipse.adt.internal.editors.layout; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; @@ -37,6 +37,7 @@ import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -62,7 +63,6 @@ public final class LayoutReloadMonitor { public boolean code = false; /** any non-layout resource changes */ public boolean resources = false; - public boolean layout = false; public boolean rClass = false; public boolean localeList = false; public boolean manifest = false; @@ -174,7 +174,7 @@ public final class LayoutReloadMonitor { boolean hasAndroidNature = false; try { - hasAndroidNature = project.hasNature(AndroidConstants.NATURE_DEFAULT); + hasAndroidNature = project.hasNature(AdtConstants.NATURE_DEFAULT); } catch (CoreException e) { // do nothing if the nature cannot be queried. return; @@ -192,7 +192,7 @@ public final class LayoutReloadMonitor { for (IProject p : referencingProjects) { try { - hasAndroidNature = p.hasNature(AndroidConstants.NATURE_DEFAULT); + hasAndroidNature = p.hasNature(AdtConstants.NATURE_DEFAULT); } catch (CoreException e) { // do nothing if the nature cannot be queried. continue; @@ -221,7 +221,7 @@ public final class LayoutReloadMonitor { // here we only care about code change (so change for .class files). // Resource changes is handled by the IResourceListener. - if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) { + if (AdtConstants.EXT_CLASS.equals(file.getFileExtension())) { if (file.getName().matches("R[\\$\\.](.*)")) { // this is a R change! if (changeFlags == null) { @@ -353,21 +353,17 @@ public final class LayoutReloadMonitor { // now check that the file is *NOT* a layout file (those automatically trigger a layout // reload and we don't want to do it twice.) - ResourceType[] resTypes = file.getResourceTypes(); + Collection<ResourceType> resTypes = file.getResourceTypes(); // it's unclear why but there has been cases of resTypes being empty! - if (resTypes.length > 0) { + if (resTypes.size() > 0) { // this is a resource change, that may require a layout redraw! if (changeFlags == null) { changeFlags = new ChangeFlags(); mProjectFlags.put(project, changeFlags); } - if (resTypes[0] != ResourceType.LAYOUT) { - changeFlags.resources = true; - } else { - changeFlags.layout = true; - } + changeFlags.resources = true; } } }; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/MatchingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/MatchingStrategy.java index c6c5261..4ef249d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/MatchingStrategy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/MatchingStrategy.java @@ -16,9 +16,9 @@ package com.android.ide.eclipse.adt.internal.editors.layout; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; +import com.android.ide.common.resources.ResourceFolder; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.resources.ResourceFolderType; import org.eclipse.core.resources.IFile; import org.eclipse.ui.IEditorInput; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java index 976dfaf..7383659 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java @@ -16,11 +16,24 @@ package com.android.ide.eclipse.adt.internal.editors.layout; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_PKG_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.CALENDAR_VIEW; +import static com.android.ide.common.layout.LayoutConstants.EXPANDABLE_LIST_VIEW; +import static com.android.ide.common.layout.LayoutConstants.LIST_VIEW; + +import com.android.ide.common.rendering.LayoutLibrary; +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.legacy.LegacyCallback; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectClassLoader; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; @@ -43,7 +56,6 @@ import java.util.TreeSet; * {@link ILegacyCallback} */ public final class ProjectCallback extends LegacyCallback { - private final HashMap<String, Class<?>> mLoadedClasses = new HashMap<String, Class<?>>(); private final Set<String> mMissingClasses = new TreeSet<String>(); private final Set<String> mBrokenClasses = new TreeSet<String>(); @@ -54,16 +66,23 @@ public final class ProjectCallback extends LegacyCallback { private String mNamespace; private ProjectClassLoader mLoader = null; private LayoutLog mLogger; + private LayoutLibrary mLayoutLib; + + private String mLayoutName; + private ILayoutPullParser mLayoutEmbeddedParser; + /** * Creates a new {@link ProjectCallback} to be used with the layout lib. * - * @param classLoader The class loader that was used to load layoutlib.jar + * @param layoutLib The layout library this callback is going to be invoked from * @param projectRes the {@link ProjectResources} for the project. * @param project the project. */ - public ProjectCallback(ClassLoader classLoader, ProjectResources projectRes, IProject project) { - mParentClassLoader = classLoader; + public ProjectCallback(LayoutLibrary layoutLib, + ProjectResources projectRes, IProject project) { + mLayoutLib = layoutLib; + mParentClassLoader = layoutLib.getClassLoader(); mProjectRes = projectRes; mProject = project; } @@ -199,7 +218,7 @@ public final class ProjectCallback extends LegacyCallback { ManifestData manifestData = AndroidManifestHelper.parseForData(mProject); if (manifestData != null) { String javaPackage = manifestData.getPackage(); - mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage); + mNamespace = String.format(AdtConstants.NS_CUSTOM_RESOURCES, javaPackage); } } @@ -208,7 +227,7 @@ public final class ProjectCallback extends LegacyCallback { public Pair<ResourceType, String> resolveResourceId(int id) { if (mProjectRes != null) { - return mProjectRes.resolveResourceValue(id); + return mProjectRes.resolveResourceId(id); } return null; @@ -216,7 +235,7 @@ public final class ProjectCallback extends LegacyCallback { public String resolveResourceId(int[] id) { if (mProjectRes != null) { - return mProjectRes.resolveResourceValue(id); + return mProjectRes.resolveStyleable(id); } return null; @@ -224,7 +243,7 @@ public final class ProjectCallback extends LegacyCallback { public Integer getResourceId(ResourceType type, String name) { if (mProjectRes != null) { - return mProjectRes.getResourceValue(type, name); + return mProjectRes.getResourceId(type, name); } return null; @@ -336,4 +355,151 @@ public final class ProjectCallback extends LegacyCallback { constructor.setAccessible(true); return constructor.newInstance(constructorParameters); } + + public void setLayoutParser(String layoutName, ILayoutPullParser layoutParser) { + mLayoutName = layoutName; + mLayoutEmbeddedParser = layoutParser; + } + + public ILayoutPullParser getParser(String layoutName) { + if (layoutName.equals(mLayoutName)) { + return mLayoutEmbeddedParser; + } + + return null; + } + + public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, + ResourceReference itemRef, + int fullPosition, int typePosition, int fullChildPosition, int typeChildPosition, + ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) { + + // Special case for the palette preview + if (viewAttribute == ViewAttribute.TEXT + && adapterView.getName().startsWith("android_widget_")) { //$NON-NLS-1$ + String name = adapterView.getName(); + if (viewRef.getName().equals("text2")) { //$NON-NLS-1$ + return "Sub Item"; + } + if (fullPosition == 0) { + String viewName = name.substring("android_widget_".length()); + if (viewName.equals(EXPANDABLE_LIST_VIEW)) { + return "ExpandableList"; // ExpandableListView is too wide, character-wraps + } + return viewName; + } else { + return "Next Item"; + } + } + + if (itemRef.isFramework()) { + // Special case for list_view_item_2 and friends + if (viewRef.getName().equals("text2")) { //$NON-NLS-1$ + return "Sub Item " + (fullPosition + 1); + } + } + + if (viewAttribute == ViewAttribute.TEXT && ((String) defaultValue).length() == 0) { + return "Item " + (fullPosition + 1); + } + + return null; + } + + /** + * For the given class, finds and returns the nearest super class which is a ListView + * or an ExpandableListView, or returns null. + * + * @param clz the class of the view object + * @return the fully qualified class name of the list view ancestor, or null if there + * is no list view ancestor + */ + public static String getListViewFqcn(Class<?> clz) { + String fqcn = clz.getName(); + if (fqcn.endsWith(LIST_VIEW)) { // including EXPANDABLE_LIST_VIEW + return fqcn; + } else if (fqcn.startsWith(ANDROID_PKG_PREFIX)) { + return null; + } + Class<?> superClass = clz.getSuperclass(); + if (superClass != null) { + return getListViewFqcn(superClass); + } else { + // Should not happen; we would have encountered android.view.View first, + // and it should have been covered by the ANDROID_PKG_PREFIX case above. + return null; + } + } + + /** + * Looks at the parent-chain of the view and if it finds a custom view, or a + * CalendarView, within the given distance then it returns true. A ListView within a + * CalendarView should not be assigned a custom list view type because it sets its own + * and then attempts to cast the layout to its own type which would fail if the normal + * default list item binding is used. + */ + private boolean isWithinIllegalParent(Object viewObject, int depth) { + String fqcn = viewObject.getClass().getName(); + if (fqcn.endsWith(CALENDAR_VIEW) || !fqcn.startsWith(ANDROID_PKG_PREFIX)) { + return true; + } + + if (depth > 0) { + Result result = mLayoutLib.getViewParent(viewObject); + if (result.isSuccess()) { + Object parent = result.getData(); + if (parent != null) { + return isWithinIllegalParent(parent, depth -1); + } + } + } + + return false; + } + + public AdapterBinding getAdapterBinding(ResourceReference adapterView, Object adapterCookie, + Object viewObject) { + // Look for user-recorded preference for layout to be used for previews + if (adapterCookie instanceof UiViewElementNode) { + UiViewElementNode uiNode = (UiViewElementNode) adapterCookie; + LayoutMetadata metadata = LayoutMetadata.get(); + AdapterBinding binding = metadata.getNodeBinding(viewObject, uiNode); + if (binding != null) { + return binding; + } + } + + if (viewObject == null) { + return null; + } + + // Is this a ListView or ExpandableListView? If so, return its fully qualified + // class name, otherwise return null. This is used to filter out other types + // of AdapterViews (such as Spinners) where we don't want to use the list item + // binding. + String listFqcn = getListViewFqcn(viewObject.getClass()); + if (listFqcn == null) { + return null; + } + + // Is this ListView nested within an "illegal" container, such as a CalendarView? + // If so, don't change the bindings below. Some views, such as CalendarView, and + // potentially some custom views, might be doing specific things with the ListView + // that could break if we add our own list binding, so for these leave the list + // alone. + if (isWithinIllegalParent(viewObject, 2)) { + return null; + } + + AdapterBinding binding = new AdapterBinding(12); + if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) { + binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_EXPANDABLE_LIST_ITEM, + true /* isFramework */, 1)); + } else { + binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_LIST_ITEM, + true /* isFramework */, 1)); + } + + return binding; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/WidgetPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/WidgetPullParser.java index 153a2d2..07e6a91 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/WidgetPullParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/WidgetPullParser.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout; import com.android.ide.common.rendering.api.ILayoutPullParser; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; import com.android.sdklib.SdkConstants; @@ -43,7 +43,7 @@ public class WidgetPullParser extends BasePullParser { public WidgetPullParser(ViewElementDescriptor descriptor) { mDescriptor = descriptor; - String[] segments = mDescriptor.getFullClassName().split(AndroidConstants.RE_DOT); + String[] segments = mDescriptor.getFullClassName().split(AdtConstants.RE_DOT); mAttributes[0][1] = segments[segments.length-1]; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java index 1dc5c27..d9b3911 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigEditDialog.java @@ -16,14 +16,14 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration; +import com.android.ide.common.resources.configuration.DockModeQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.LanguageQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.ide.common.resources.configuration.VersionQualifier; import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.resources.configurations.DockModeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NightModeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQualifier; import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java index 20aacba..9812c2a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigManagerDialog.java @@ -17,8 +17,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration; import com.android.ddmuilib.TableHelper; +import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; import com.android.ide.eclipse.adt.internal.sdk.LayoutDeviceManager; import com.android.ide.eclipse.adt.internal.sdk.Sdk; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java index 0e927fa..d304303 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java @@ -16,24 +16,30 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE; + import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.configuration.DockModeQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.LanguageQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; +import com.android.ide.common.resources.configuration.VersionQualifier; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.resources.configurations.DockModeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NightModeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQualifier; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice; @@ -43,14 +49,19 @@ import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig; import com.android.resources.Density; import com.android.resources.DockMode; import com.android.resources.NightMode; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenSize; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.util.SparseIntArray; +import com.android.util.Pair; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.draw2d.geometry.Rectangle; @@ -66,9 +77,12 @@ import org.eclipse.swt.widgets.Label; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.SortedSet; /** @@ -97,9 +111,23 @@ import java.util.SortedSet; * loading.<br> */ public class ConfigurationComposite extends Composite { + private final static String SEP = ":"; //$NON-NLS-1$ + private final static String SEP_LOCALE = "-"; //$NON-NLS-1$ + /** + * Setting name for project-wide setting controlling rendering target and locale which + * is shared for all files + */ + public final static QualifiedName NAME_RENDER_STATE = + new QualifiedName(AdtPlugin.PLUGIN_ID, "render");//$NON-NLS-1$ + + /** + * Settings name for file-specific configuration preferences, such as which theme or + * device to render the current layout with + */ public final static QualifiedName NAME_CONFIG_STATE = new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$ + private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$ private final static int LOCALE_LANG = 0; @@ -116,7 +144,13 @@ public class ConfigurationComposite extends Composite { private Combo mThemeCombo; private Combo mTargetCombo; - private int mPlatformThemeCount = 0; + /** + * List of booleans, matching item for item the theme names in the mThemeCombo + * combobox, where each boolean represents whether the corresponding theme is a + * project theme + */ + private List<Boolean> mIsProjectTheme = new ArrayList<Boolean>(40); + /** updates are disabled if > 0 */ private int mDisableUpdates = 0; @@ -164,6 +198,12 @@ public class ConfigurationComposite extends Composite { void onConfigurationChange(); /** + * Called after a device has changed (in addition to {@link #onConfigurationChange} + * getting called) + */ + void onDevicePostChange(); + + /** * Called when the current theme changes. The theme can be queried with * {@link ConfigurationComposite#getTheme()}. */ @@ -187,11 +227,12 @@ public class ConfigurationComposite extends Composite { */ void onRenderingTargetPostChange(IAndroidTarget target); - ProjectResources getProjectResources(); - ProjectResources getFrameworkResources(); - ProjectResources getFrameworkResources(IAndroidTarget target); + ResourceRepository getProjectResources(); + ResourceRepository getFrameworkResources(); + ResourceRepository getFrameworkResources(IAndroidTarget target); Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources(); Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources(); + String getIncludedWithin(); } /** @@ -199,9 +240,6 @@ public class ConfigurationComposite extends Composite { * rendering to its original configuration. */ private class ConfigState { - private final static String SEP = ":"; //$NON-NLS-1$ - private final static String SEP_LOCALE = "-"; //$NON-NLS-1$ - LayoutDevice device; String configName; ResourceQualifier[] locale; @@ -220,7 +258,7 @@ public class ConfigurationComposite extends Composite { sb.append(SEP); sb.append(configName); sb.append(SEP); - if (locale != null) { + if (isLocaleSpecificLayout() && locale != null) { if (locale[0] != null && locale[1] != null) { // locale[0]/[1] can be null sometimes when starting Eclipse sb.append(((LanguageQualifier) locale[0]).getValue()); @@ -235,10 +273,10 @@ public class ConfigurationComposite extends Composite { sb.append(SEP); sb.append(night.getResourceValue()); sb.append(SEP); - if (target != null) { - sb.append(targetToString(target)); - sb.append(SEP); - } + + // We used to store the render target here in R9. Leave a marker + // to ensure that we don't reuse this slot; add new extra fields after it. + sb.append(SEP); } return sb.toString(); @@ -254,6 +292,8 @@ public class ConfigurationComposite extends Composite { if (config != null) { configName = values[1]; + // Load locale. Note that this can get overwritten by the + // project-wide settings read below. locale = new ResourceQualifier[2]; String locales[] = values[2].split(SEP_LOCALE); if (locales.length >= 2) { @@ -275,12 +315,17 @@ public class ConfigurationComposite extends Composite { night = NightMode.NOTNIGHT; } - if (values.length == 7 && mTargetList != null) { - target = stringToTarget(values[6]); - } else { - // No render target stored; try to find the best default - target = findDefaultRenderTarget(); + // element 7/values[6]: used to store render target in R9. + // No longer stored here. If adding more data, make + // sure you leave 7 alone. + + Pair<ResourceQualifier[], IAndroidTarget> pair = loadRenderState(); + + // We only use the "global" setting + if (!isLocaleSpecificLayout()) { + locale = pair.getFirst(); } + target = pair.getSecond(); return true; } @@ -293,67 +338,40 @@ public class ConfigurationComposite extends Composite { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - if (device != null) { - sb.append(device.getName()); - } else { - sb.append("null"); - } - sb.append(SEP); - sb.append(configName); - sb.append(SEP); - if (locale != null) { - sb.append(((LanguageQualifier) locale[0]).getValue()); - sb.append(SEP_LOCALE); - sb.append(((RegionQualifier) locale[1]).getValue()); - } - sb.append(SEP); - sb.append(theme); - sb.append(SEP); - sb.append(dock.getResourceValue()); - sb.append(SEP); - sb.append(night.getResourceValue()); - sb.append(SEP); - - if (target != null) { - sb.append(targetToString(target)); - sb.append(SEP); - } - - return sb.toString(); + return getData(); } + } - /** - * Returns a String id to represent an {@link IAndroidTarget} which can be translated - * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id - * will never contain the {@link #SEP} character. - * - * @param target the target to return an id for - * @return an id for the given target; never null - */ - private String targetToString(IAndroidTarget target) { - return target.getFullName().replace(SEP, ""); //$NON-NLS-1$ - } + /** + * Returns a String id to represent an {@link IAndroidTarget} which can be translated + * back to an {@link IAndroidTarget} by the matching {@link #stringToTarget}. The id + * will never contain the {@link #SEP} character. + * + * @param target the target to return an id for + * @return an id for the given target; never null + */ + private String targetToString(IAndroidTarget target) { + return target.getFullName().replace(SEP, ""); //$NON-NLS-1$ + } - /** - * Returns an {@link IAndroidTarget} that corresponds to the given id that was - * originally returned by {@link #targetToString}. May be null, if the platform is no - * longer available, or if the platform list has not yet been initialized. - * - * @param id the id that corresponds to the desired platform - * @return an {@link IAndroidTarget} that matches the given id, or null - */ - private IAndroidTarget stringToTarget(String id) { - if (mTargetList != null && mTargetList.size() > 0) { - for (IAndroidTarget target : mTargetList) { - if (id.equals(targetToString(target))) { - return target; - } + /** + * Returns an {@link IAndroidTarget} that corresponds to the given id that was + * originally returned by {@link #targetToString}. May be null, if the platform is no + * longer available, or if the platform list has not yet been initialized. + * + * @param id the id that corresponds to the desired platform + * @return an {@link IAndroidTarget} that matches the given id, or null + */ + private IAndroidTarget stringToTarget(String id) { + if (mTargetList != null && mTargetList.size() > 0) { + for (IAndroidTarget target : mTargetList) { + if (id.equals(targetToString(target))) { + return target; } } - - return null; } + + return null; } /** @@ -377,11 +395,11 @@ public class ConfigurationComposite extends Composite { GridLayout gl; GridData gd; - int cols = 6; // device+config+separator*2+theme+apiLevel + int cols = 7; // device+config+dock+day+separator*2+theme - // ---- First line: locale, day/night, dock, editing config display. + // ---- First line: editing config display, locale, theme, create-button Composite labelParent = new Composite(this, SWT.NONE); - labelParent.setLayout(gl = new GridLayout(6, false)); + labelParent.setLayout(gl = new GridLayout(5, false)); gl.marginWidth = gl.marginHeight = 0; gl.marginTop = 3; labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); @@ -407,25 +425,13 @@ public class ConfigurationComposite extends Composite { mLocaleCombo.add("Locale"); //$NON-NLS-1$ // Dummy place holders mLocaleCombo.add("Locale"); //$NON-NLS-1$ - mDockCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY); - for (DockMode mode : DockMode.values()) { - mDockCombo.add(mode.getLongDisplayValue()); - } - mDockCombo.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onDockChange(); - } - }); - - mNightCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY); - for (NightMode mode : NightMode.values()) { - mNightCombo.add(mode.getLongDisplayValue()); - } - mNightCombo.addSelectionListener(new SelectionAdapter() { + mTargetCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY); + mTargetCombo.add("Android AOSP"); //$NON-NLS-1$ // Dummy place holders + mTargetCombo.add("Android AOSP"); //$NON-NLS-1$ + mTargetCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - onDayChange(); + onRenderingTargetChange(); } }); @@ -474,31 +480,41 @@ public class ConfigurationComposite extends Composite { GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); gd.heightHint = 0; - mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN); - mThemeCombo.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mThemeCombo.setEnabled(false); - - mThemeCombo.addSelectionListener(new SelectionAdapter() { + mDockCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + mDockCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL + | GridData.GRAB_HORIZONTAL)); + for (DockMode mode : DockMode.values()) { + mDockCombo.add(mode.getLongDisplayValue()); + } + mDockCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - onThemeChange(); + onDockChange(); } }); - // second separator - separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL); - separator.setLayoutData(gd = new GridData( - GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); - gd.heightHint = 0; + mNightCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + mNightCombo.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL + | GridData.GRAB_HORIZONTAL)); + for (NightMode mode : NightMode.values()) { + mNightCombo.add(mode.getLongDisplayValue()); + } + mNightCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onDayChange(); + } + }); - mTargetCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - mTargetCombo.setLayoutData(new GridData( + mThemeCombo = new Combo(this, SWT.READ_ONLY | SWT.DROP_DOWN); + mThemeCombo.setLayoutData(new GridData( GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mTargetCombo.addSelectionListener(new SelectionAdapter() { + mThemeCombo.setEnabled(false); + + mThemeCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - onRenderingTargetChange(); + onThemeChange(); } }); } @@ -679,8 +695,6 @@ public class ConfigurationComposite extends Composite { loadedConfigData = mState.setData(data); } - // update the themes and locales. - updateThemes(); updateLocales(); // If the current state was loaded from the persistent storage, we update the @@ -710,6 +724,14 @@ public class ConfigurationComposite extends Composite { } } + // Update themes. This is done after updating the devices above, + // since we want to look at the chosen device size to decide + // what the default theme (for example, with Honeycomb we choose + // Holo as the default theme but only if the screen size is XLARGE + // (and of course only if the manifest does not specify another + // default theme). + updateThemes(); + // update the string showing the config value updateConfigDisplay(mEditedConfig); @@ -767,6 +789,26 @@ public class ConfigurationComposite extends Composite { } } + private static class ConfigMatch { + final FolderConfiguration testConfig; + final LayoutDevice device; + final String name; + final ConfigBundle bundle; + + public ConfigMatch(FolderConfiguration testConfig, + LayoutDevice device, String name, ConfigBundle bundle) { + this.testConfig = testConfig; + this.device = device; + this.name = name; + this.bundle = bundle; + } + + @Override + public String toString() { + return device.getName() + " - " + name; + } + } + /** * Finds a device/config that can display {@link #mEditedConfig}. * <p/>Once found the device and config combos are set to the config. @@ -775,23 +817,19 @@ public class ConfigurationComposite extends Composite { * the current config. This must only be true if the current config is compatible. */ private void findAndSetCompatibleConfig(boolean favorCurrentConfig) { - LayoutDevice anyDeviceMatch = null; // a compatible device/config/locale - String anyConfigMatchName = null; - ConfigBundle anyConfigBundle = null; + // list of compatible device/config/locale + List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>(); - LayoutDevice bestDeviceMatch = null; // an actual best match - String bestConfigMatchName = null; - ConfigBundle bestConfigBundle = null; - - FolderConfiguration testConfig = new FolderConfiguration(); + // list of actual best match (ie the file is a best match for the device/config) + List<ConfigMatch> bestMatches = new ArrayList<ConfigMatch>(); // get a locale that match the host locale roughly (may not be exact match on the region.) int localeHostMatch = getLocaleMatch(); // build a list of combinations of non standard qualifiers to add to each device's // qualifier set when testing for a match. - // These qualifiers are: locale, nightmode, car dock. - List<ConfigBundle> addConfig = new ArrayList<ConfigBundle>(); + // These qualifiers are: locale, night-mode, car dock. + List<ConfigBundle> configBundles = new ArrayList<ConfigBundle>(200); // If the edited file has locales, then we have to select a matching locale from // the list. @@ -816,48 +854,44 @@ public class ConfigurationComposite extends Composite { bundle.config.setRegionQualifier((RegionQualifier) l[LOCALE_REGION]); bundle.localeIndex = i; - addConfig.add(bundle); + configBundles.add(bundle); } // add the dock mode to the bundle combinations. - addDockModeToBundles(addConfig); + addDockModeToBundles(configBundles); // add the night mode to the bundle combinations. - addNightModeToBundles(addConfig); + addNightModeToBundles(configBundles); - mainloop: for (LayoutDevice device : mDeviceList) { + for (LayoutDevice device : mDeviceList) { for (DeviceConfig config : device.getConfigs()) { - // loop on the list of qualifier adds - for (ConfigBundle bundle : addConfig) { - // set the base config. This erase all data in testConfig. + // loop on the list of config bundles to create full configurations. + for (ConfigBundle bundle : configBundles) { + // create a new config with device config + FolderConfiguration testConfig = new FolderConfiguration(); testConfig.set(config.getConfig()); - // add on top of it, the extra qualifiers + // add on top of it, the extra qualifiers from the bundle testConfig.add(bundle.config); if (mEditedConfig.isMatchFor(testConfig)) { // this is a basic match. record it in case we don't find a match // where the edited file is a best config. - if (anyDeviceMatch == null) { - anyDeviceMatch = device; - anyConfigMatchName = config.getName(); - anyConfigBundle = bundle; - } + anyMatches.add(new ConfigMatch(testConfig, device, config.getName(), + bundle)); if (isCurrentFileBestMatchFor(testConfig)) { // this is what we want. - bestDeviceMatch = device; - bestConfigMatchName = config.getName(); - bestConfigBundle = bundle; - break mainloop; + bestMatches.add(new ConfigMatch(testConfig, device, config.getName(), + bundle)); } } } } } - if (bestDeviceMatch == null) { + if (bestMatches.size() == 0) { if (favorCurrentConfig) { // quick check if (mEditedConfig.isMatchFor(mCurrentConfig) == false) { @@ -873,13 +907,14 @@ public class ConfigurationComposite extends Composite { String.format( "Displaying it with '%1$s'", mCurrentConfig.toDisplayString())); - } else if (anyDeviceMatch != null) { - // select the device anyway. - selectDevice(mState.device = anyDeviceMatch); - fillConfigCombo(anyConfigMatchName); - mLocaleCombo.select(anyConfigBundle.localeIndex); - mDockCombo.select(anyConfigBundle.dockModeIndex); - mNightCombo.select(anyConfigBundle.nightModeIndex); + } else if (anyMatches.size() > 0) { + // select the best device anyway. + ConfigMatch match = selectConfigMatch(anyMatches); + selectDevice(mState.device = match.device); + fillConfigCombo(match.name); + mLocaleCombo.select(match.bundle.localeIndex); + mDockCombo.select(match.bundle.dockModeIndex); + mNightCombo.select(match.bundle.nightModeIndex); // TODO: display a better warning! computeCurrentConfig(); @@ -897,14 +932,123 @@ public class ConfigurationComposite extends Composite { // and replace whatever qualifier required by the layout file. } } else { - selectDevice(mState.device = bestDeviceMatch); - fillConfigCombo(bestConfigMatchName); - mLocaleCombo.select(bestConfigBundle.localeIndex); - mDockCombo.select(bestConfigBundle.dockModeIndex); - mNightCombo.select(bestConfigBundle.nightModeIndex); + ConfigMatch match = selectConfigMatch(bestMatches); + selectDevice(mState.device = match.device); + fillConfigCombo(match.name); + mLocaleCombo.select(match.bundle.localeIndex); + mDockCombo.select(match.bundle.dockModeIndex); + mNightCombo.select(match.bundle.nightModeIndex); } } + /** + * Note: this comparator imposes orderings that are inconsistent with equals. + */ + private static class TabletConfigComparator implements Comparator<ConfigMatch> { + public int compare(ConfigMatch o1, ConfigMatch o2) { + ScreenSize ss1 = o1.testConfig.getScreenSizeQualifier().getValue(); + ScreenSize ss2 = o2.testConfig.getScreenSizeQualifier().getValue(); + + // X-LARGE is better than all others (which are considered identical) + // if both X-LARGE, then LANDSCAPE is better than all others (which are identical) + + if (ss1 == ScreenSize.XLARGE) { + if (ss2 == ScreenSize.XLARGE) { + ScreenOrientation so1 = + o1.testConfig.getScreenOrientationQualifier().getValue(); + ScreenOrientation so2 = + o2.testConfig.getScreenOrientationQualifier().getValue(); + + if (so1 == ScreenOrientation.LANDSCAPE) { + if (so2 == ScreenOrientation.LANDSCAPE) { + return 0; + } else { + return -1; + } + } else if (so2 == ScreenOrientation.LANDSCAPE) { + return 1; + } else { + return 0; + } + } else { + return -1; + } + } else if (ss2 == ScreenSize.XLARGE) { + return 1; + } else { + return 0; + } + } + } + + /** + * Note: this comparator imposes orderings that are inconsistent with equals. + */ + private static class PhoneConfigComparator implements Comparator<ConfigMatch> { + + private SparseIntArray mDensitySort = new SparseIntArray(4); + + public PhoneConfigComparator() { + // put the sort order for the density. + mDensitySort.put(Density.HIGH.getDpiValue(), 1); + mDensitySort.put(Density.MEDIUM.getDpiValue(), 2); + mDensitySort.put(Density.XHIGH.getDpiValue(), 3); + mDensitySort.put(Density.LOW.getDpiValue(), 4); + } + + public int compare(ConfigMatch o1, ConfigMatch o2) { + int dpi1 = Density.DEFAULT_DENSITY; + if (o1.testConfig.getPixelDensityQualifier() != null) { + dpi1 = o1.testConfig.getPixelDensityQualifier().getValue().getDpiValue(); + dpi1 = mDensitySort.get(dpi1, 100 /* valueIfKeyNotFound*/); + } + + int dpi2 = Density.DEFAULT_DENSITY; + if (o2.testConfig.getPixelDensityQualifier() != null) { + dpi2 = o2.testConfig.getPixelDensityQualifier().getValue().getDpiValue(); + dpi2 = mDensitySort.get(dpi2, 100 /* valueIfKeyNotFound*/); + } + + if (dpi1 == dpi2) { + // portrait is better + ScreenOrientation so1 = + o1.testConfig.getScreenOrientationQualifier().getValue(); + ScreenOrientation so2 = + o2.testConfig.getScreenOrientationQualifier().getValue(); + + if (so1 == ScreenOrientation.PORTRAIT) { + if (so2 == ScreenOrientation.PORTRAIT) { + return 0; + } else { + return -1; + } + } else if (so2 == ScreenOrientation.PORTRAIT) { + return 1; + } else { + return 0; + } + } + + return dpi1 - dpi2; + } + } + + private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) { + // API 11+ : look for a x-large device. + if (mProjectTarget.getVersion().getApiLevel() >= 11) { + // TODO: Revisit this once phones can run higher APIs. + // Maybe check the compatible-screen tag in the manifest to figure out what kind of + // device should be used for display. + Collections.sort(matches, new TabletConfigComparator()); + } else { + // lets look for a high density device + Collections.sort(matches, new PhoneConfigComparator()); + } + + // the list has been sorted so that the first item is the best config + return matches.get(0); + } + private void addDockModeToBundles(List<ConfigBundle> addConfig) { ArrayList<ConfigBundle> list = new ArrayList<ConfigBundle>(); @@ -1008,8 +1152,11 @@ public class ConfigurationComposite extends Composite { * Finds a locale matching the config from a file. * @param language the language qualifier or null if none is set. * @param region the region qualifier or null if none is set. + * @return true if there was a change in the combobox as a result of applying the locale */ - private void setLocaleCombo(ResourceQualifier language, ResourceQualifier region) { + private boolean setLocaleCombo(ResourceQualifier language, ResourceQualifier region) { + boolean changed = false; + // find the locale match. Since the locale list is based on the content of the // project resources there must be an exact match. // The only trick is that the region could be null in the fileConfig but in our @@ -1026,16 +1173,24 @@ public class ConfigurationComposite extends Composite { if (RegionQualifier.FAKE_REGION_VALUE.equals( ((RegionQualifier)locale[LOCALE_REGION]).getValue())) { // match! - mLocaleCombo.select(i); + if (mLocaleCombo.getSelectionIndex() != i) { + mLocaleCombo.select(i); + changed = true; + } break; } } else if (region.equals(locale[LOCALE_REGION])) { // match! - mLocaleCombo.select(i); + if (mLocaleCombo.getSelectionIndex() != i) { + mLocaleCombo.select(i); + changed = true; + } break; } } } + + return changed; } private void updateConfigDisplay(FolderConfiguration fileConfig) { @@ -1111,12 +1266,12 @@ public class ConfigurationComposite extends Composite { boolean hasLocale = false; // get the languages from the project. - ProjectResources project = mListener.getProjectResources(); + ResourceRepository projectRes = mListener.getProjectResources(); // in cases where the opened file is not linked to a project, this could be null. - if (project != null) { + if (projectRes != null) { // now get the languages from the project. - languages = project.getLanguages(); + languages = projectRes.getLanguages(); for (String language : languages) { hasLocale = true; @@ -1124,7 +1279,7 @@ public class ConfigurationComposite extends Composite { LanguageQualifier langQual = new LanguageQualifier(language); // find the matching regions and add them - SortedSet<String> regions = project.getRegions(language); + SortedSet<String> regions = projectRes.getRegions(language); for (String region : regions) { mLocaleCombo.add( String.format("%1$s / %2$s", language, region)); //$NON-NLS-1$ @@ -1215,22 +1370,120 @@ public class ConfigurationComposite extends Composite { return; // can't do anything w/o it. } - ProjectResources frameworkProject = mListener.getFrameworkResources(getRenderingTarget()); + ResourceRepository frameworkRes = mListener.getFrameworkResources(getRenderingTarget()); mDisableUpdates++; try { // Reset the combo mThemeCombo.removeAll(); - mPlatformThemeCount = 0; + mIsProjectTheme.clear(); ArrayList<String> themes = new ArrayList<String>(); + String includedIn = mListener != null ? mListener.getIncludedWithin() : null; + + // First list any themes that are declared by the manifest + if (mEditedFile != null) { + IProject project = mEditedFile.getProject(); + ManifestInfo manifest = ManifestInfo.get(project); + + // Look up the screen size for the current configuration + ScreenSize screenSize = null; + if (mState.device != null) { + List<DeviceConfig> configs = mState.device.getConfigs(); + for (DeviceConfig config : configs) { + ScreenSizeQualifier qualifier = + config.getConfig().getScreenSizeQualifier(); + screenSize = qualifier.getValue(); + break; + } + } + // Look up the default/fallback theme to use for this project (which + // depends on the screen size when no particular theme is specified + // in the manifest) + String defaultTheme = manifest.getDefaultTheme(mState.target, screenSize); + + Map<String, String> activityThemes = manifest.getActivityThemes(); + String pkg = manifest.getPackage(); + String preferred = null; + boolean isIncluded = includedIn != null; + if (mState.theme == null || isIncluded) { + String layoutName = ResourceHelper.getLayoutName(mEditedFile); + + // If we are rendering a layout in included context, pick the theme + // from the outer layout instead + if (includedIn != null) { + layoutName = includedIn; + } + + String activity = ManifestInfo.guessActivity(project, layoutName, pkg); + if (activity != null) { + preferred = activityThemes.get(activity); + } + if (preferred == null) { + preferred = defaultTheme; + } + String preferredTheme = ResourceHelper.styleToTheme(preferred); + if (includedIn == null) { + mState.theme = preferredTheme; + } + boolean isProjectTheme = !preferred.startsWith(PREFIX_ANDROID_STYLE); + mThemeCombo.add(preferredTheme); + mIsProjectTheme.add(Boolean.valueOf(isProjectTheme)); + + mThemeCombo.add(THEME_SEPARATOR); + mIsProjectTheme.add(Boolean.FALSE); + } + + // Create a sorted list of unique themes referenced in the manifest + // (sort alphabetically, but place the preferred theme at the + // top of the list) + Set<String> themeSet = new HashSet<String>(activityThemes.values()); + themeSet.add(defaultTheme); + List<String> themeList = new ArrayList<String>(themeSet); + final String first = preferred; + Collections.sort(themeList, new Comparator<String>() { + public int compare(String s1, String s2) { + if (s1 == first) { + return -1; + } else if (s1 == first) { + return 1; + } else { + return s1.compareTo(s2); + } + } + }); + + if (themeList.size() > 1 || + (themeList.size() == 1 && (preferred == null || + !preferred.equals(themeList.get(0))))) { + for (String style : themeList) { + String theme = ResourceHelper.styleToTheme(style); + + // Initialize the chosen theme to the first item + // in the used theme list (that's what would be chosen + // anyway) such that we stop attempting to look up + // the associated activity (during initialization, + // this method can be called repeatedly.) + if (mState.theme == null) { + mState.theme = theme; + } + + boolean isProjectTheme = !style.startsWith(PREFIX_ANDROID_STYLE); + mThemeCombo.add(theme); + mIsProjectTheme.add(Boolean.valueOf(isProjectTheme)); + } + mThemeCombo.add(THEME_SEPARATOR); + mIsProjectTheme.add(Boolean.FALSE); + } + } // get the themes, and languages from the Framework. - if (frameworkProject != null) { + int platformThemeCount = 0; + if (frameworkRes != null) { // get the configured resources for the framework Map<ResourceType, Map<String, ResourceValue>> frameworResources = - frameworkProject.getConfiguredResources(getCurrentConfig()); + frameworkRes.getConfiguredResources(getCurrentConfig()); if (frameworResources != null) { // get the styles. @@ -1242,7 +1495,6 @@ public class ConfigurationComposite extends Composite { String name = value.getName(); if (name.startsWith("Theme.") || name.equals("Theme")) { themes.add(value.getName()); - mPlatformThemeCount++; } } @@ -1251,17 +1503,18 @@ public class ConfigurationComposite extends Composite { for (String theme : themes) { mThemeCombo.add(theme); + mIsProjectTheme.add(Boolean.FALSE); } - mPlatformThemeCount = themes.size(); + platformThemeCount = themes.size(); themes.clear(); } } // now get the themes and languages from the project. - ProjectResources project = mListener.getProjectResources(); + ResourceRepository projectRes = mListener.getProjectResources(); // in cases where the opened file is not linked to a project, this could be null. - if (project != null) { + if (projectRes != null) { // get the configured resources for the project Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = mListener.getConfiguredProjectResources(); @@ -1281,14 +1534,16 @@ public class ConfigurationComposite extends Composite { } // sort them and add them the to the combo. - if (mPlatformThemeCount > 0 && themes.size() > 0) { + if (platformThemeCount > 0 && themes.size() > 0) { mThemeCombo.add(THEME_SEPARATOR); + mIsProjectTheme.add(Boolean.FALSE); } Collections.sort(themes); for (String theme : themes) { mThemeCombo.add(theme); + mIsProjectTheme.add(Boolean.TRUE); } } } @@ -1297,7 +1552,7 @@ public class ConfigurationComposite extends Composite { // try to reselect the previous theme. boolean needDefaultSelection = true; - if (mState.theme != null) { + if (mState.theme != null && includedIn == null) { final int count = mThemeCombo.getItemCount(); for (int i = 0 ; i < count ; i++) { if (mState.theme.equals(mThemeCombo.getItem(i))) { @@ -1322,6 +1577,8 @@ public class ConfigurationComposite extends Composite { } finally { mDisableUpdates--; } + + assert mIsProjectTheme.size() == mThemeCombo.getItemCount(); } // ---- getters for the config selection values ---- @@ -1451,7 +1708,7 @@ public class ConfigurationComposite extends Composite { * @return true for project theme, false for framework theme */ public boolean isProjectTheme() { - return mThemeCombo.getSelectionIndex() >= mPlatformThemeCount; + return mIsProjectTheme.get(mThemeCombo.getSelectionIndex()).booleanValue(); } public IAndroidTarget getRenderingTarget() { @@ -1770,6 +2027,7 @@ public class ConfigurationComposite extends Composite { if (computeCurrentConfig() && mListener != null) { mListener.onConfigurationChange(); + mListener.onDevicePostChange(); } } @@ -1786,6 +2044,9 @@ public class ConfigurationComposite extends Composite { if (computeCurrentConfig() && mListener != null) { mListener.onConfigurationChange(); } + + // Store locale project-wide setting + saveRenderState(); } private void onDockChange() { @@ -1837,6 +2098,9 @@ public class ConfigurationComposite extends Composite { if (computeOk && mListener != null) { mListener.onConfigurationChange(); } + + // Store project-wide render-target setting + saveRenderState(); } /** @@ -1949,9 +2213,9 @@ public class ConfigurationComposite extends Composite { } // check for framework identifier. - if (parentStyle.startsWith("android:")) { + if (parentStyle.startsWith(ANDROID_NS_NAME_PREFIX)) { frameworkStyle = true; - parentStyle = parentStyle.substring("android:".length()); + parentStyle = parentStyle.substring(ANDROID_NS_NAME_PREFIX.length()); } // at this point we could have the format style/<name>. we want only the name @@ -2028,5 +2292,169 @@ public class ConfigurationComposite extends Composite { mEditedConfig = null; onXmlModelLoaded(); } -} + /** + * Syncs this configuration to the project wide locale and render target settings. The + * locale may ignore the project-wide setting if it is a locale-specific + * configuration. + * + * @return true if one or both of the toggles were changed, false if there were no + * changes + */ + public boolean syncRenderState() { + if (mEditedConfig == null) { + // Startup; ignore + return false; + } + + boolean localeChanged = false; + boolean renderTargetChanged = false; + + // When a page is re-activated, force the toggles to reflect the current project + // state + + Pair<ResourceQualifier[], IAndroidTarget> pair = loadRenderState(); + + // Only sync the locale if this layout is not already a locale-specific layout! + if (!isLocaleSpecificLayout()) { + ResourceQualifier[] locale = pair.getFirst(); + if (locale != null) { + localeChanged = setLocaleCombo(locale[0], locale[1]); + } + } + + // Sync render target + IAndroidTarget target = pair.getSecond(); + if (target != null) { + int targetIndex = mTargetList.indexOf(target); + if (targetIndex != mTargetCombo.getSelectionIndex()) { + mTargetCombo.select(targetIndex); + renderTargetChanged = true; + } + } + + if (!renderTargetChanged && !localeChanged) { + return false; + } + + // Update the locale and/or the render target. This code contains a logical + // merge of the onRenderingTargetChange() and onLocaleChange() methods, combined + // such that we don't duplicate work. + + if (renderTargetChanged) { + if (mListener != null && mRenderingTarget != null) { + mListener.onRenderingTargetPreChange(mRenderingTarget); + } + int targetIndex = mTargetCombo.getSelectionIndex(); + mRenderingTarget = mTargetList.get(targetIndex); + } + + // Compute the new configuration; we want to do this both for locale changes + // and for render targets. + boolean computeOk = computeCurrentConfig(); + + if (renderTargetChanged) { + // force a theme update to reflect the new rendering target. + // This must be done after computeCurrentConfig since it'll depend on the currentConfig + // to figure out the theme list. + updateThemes(); + + if (mListener != null && mRenderingTarget != null) { + mListener.onRenderingTargetPostChange(mRenderingTarget); + } + } + + // For both locale and render target changes + if (computeOk && mListener != null) { + mListener.onConfigurationChange(); + } + + return true; + } + + /** + * Loads the render state (the locale and the render target, which are shared among + * all the layouts meaning that changing it in one will change it in all) and returns + * the current project-wide locale and render target to be used. + * + * @return a pair of locale resource qualifiers and render target + */ + private Pair<ResourceQualifier[], IAndroidTarget> loadRenderState() { + IProject project = mEditedFile.getProject(); + try { + String data = project.getPersistentProperty(NAME_RENDER_STATE); + if (data != null) { + ResourceQualifier[] locale = null; + IAndroidTarget target = null; + + String[] values = data.split(SEP); + if (values.length == 2) { + locale = new ResourceQualifier[2]; + String locales[] = values[0].split(SEP_LOCALE); + if (locales.length >= 2) { + if (locales[0].length() > 0) { + locale[0] = new LanguageQualifier(locales[0]); + } + if (locales[1].length() > 0) { + locale[1] = new RegionQualifier(locales[1]); + } + } + target = stringToTarget(values[1]); + } + + return Pair.of(locale, target); + } + + ResourceQualifier[] any = new ResourceQualifier[] { + new LanguageQualifier(LanguageQualifier.FAKE_LANG_VALUE), + new RegionQualifier(RegionQualifier.FAKE_REGION_VALUE) + }; + + return Pair.of(any, findDefaultRenderTarget()); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + return null; + } + + /** Returns true if the current layout is locale-specific */ + private boolean isLocaleSpecificLayout() { + return mEditedConfig == null || mEditedConfig.getLanguageQualifier() != null; + } + + /** + * Saves the render state (the current locale and render target settings) into the + * project wide settings storage + */ + private void saveRenderState() { + IProject project = mEditedFile.getProject(); + try { + int index = mLocaleCombo.getSelectionIndex(); + ResourceQualifier[] locale = mLocaleList.get(index); + index = mTargetCombo.getSelectionIndex(); + IAndroidTarget target = mTargetList.get(index); + + // Generate a persistent string from locale+target + StringBuilder sb = new StringBuilder(); + if (locale != null) { + if (locale[0] != null && locale[1] != null) { + // locale[0]/[1] can be null sometimes when starting Eclipse + sb.append(((LanguageQualifier) locale[0]).getValue()); + sb.append(SEP_LOCALE); + sb.append(((RegionQualifier) locale[1]).getValue()); + } + } + sb.append(SEP); + if (target != null) { + sb.append(targetToString(target)); + sb.append(SEP); + } + + String data = sb.toString(); + project.setPersistentProperty(NAME_RENDER_STATE, data); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java index 978f114..b63785c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LayoutCreatorDialog.java @@ -16,13 +16,13 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.ResourceQualifier; import com.android.ide.eclipse.adt.internal.editors.IconFactory; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode; +import com.android.resources.ResourceFolderType; import com.android.sdkuilib.ui.GridDialog; import org.eclipse.jface.dialogs.Dialog; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java index bfc8bb0..db72ac6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java @@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.descriptors; import com.android.ide.common.resources.platform.ViewClassInfo; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.IAndroidTarget; @@ -29,6 +30,10 @@ import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.ui.ISharedImages; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; import java.util.HashMap; import java.util.List; @@ -157,16 +162,9 @@ public final class CustomViewDescriptorService { if (parentDescriptor != null) { // we have a valid parent, lets create a new ViewElementDescriptor. - ViewElementDescriptor descriptor = new ViewElementDescriptor(fqcn, - fqcn, // ui_name - fqcn, // canonical class name - null, // tooltip - null, // sdk_url - getAttributeDescriptor(type, parentDescriptor), - null, // layout attributes - null, // children - false /* mandatory */); - + String name = DescriptorsUtils.getBasename(fqcn); + ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn, + getAttributeDescriptor(type, parentDescriptor)); descriptor.setSuperClass(parentDescriptor); synchronized (mCustomDescriptorMap) { @@ -242,16 +240,9 @@ public final class CustomViewDescriptorService { if (parentDescriptor != null) { // parent class is a valid View class with a descriptor, so we create one // for this class. - ViewElementDescriptor descriptor = new ViewElementDescriptor(fqcn, - fqcn, // ui_name - fqcn, // canonical name - null, // tooltip - null, // sdk_url - getAttributeDescriptor(type, parentDescriptor), - null, // layout attributes - null, // children - false /* mandatory */); - + String name = DescriptorsUtils.getBasename(fqcn); + ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn, + getAttributeDescriptor(type, parentDescriptor)); descriptor.setSuperClass(parentDescriptor); // add it to the map @@ -290,4 +281,31 @@ public final class CustomViewDescriptorService { // TODO add the class attribute descriptors to the parent descriptors. return parentDescriptor.getAttributes(); } + + private class CustomViewDescriptor extends ViewElementDescriptor { + public CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes) { + super( + fqcn, // xml name + name, // ui name + fqcn, // full class name + fqcn, // tooltip + null, // sdk_url + attributes, + null, // layout attributes + null, // children + false // mandatory + ); + } + + @Override + public Image getGenericIcon() { + // Java source file icon. We could use the Java class icon here + // (IMG_OBJS_CLASS), but it does not work well on anything but + // white backgrounds + ISharedImages sharedImages = JavaUI.getSharedImages(); + String key = ISharedImages.IMG_OBJS_CUNIT; + ImageDescriptor descriptor = sharedImages.getImageDescriptor(key); + return descriptor.createImage(); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java index b61c069..4613492 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java @@ -42,20 +42,27 @@ import java.util.Map.Entry; public final class LayoutDescriptors implements IDescriptorProvider { /** - * The XML name of the special <include> layout tag. + * The XML name of the special {@code <include>} layout tag. * A synthetic element with that name is created as part of the view descriptors list * returned by {@link #getViewDescriptors()}. */ public static final String VIEW_INCLUDE = "include"; //$NON-NLS-1$ /** - * The XML name of the special <merge> layout tag. + * The XML name of the special {@code <merge>} layout tag. * A synthetic element with that name is created as part of the view descriptors list * returned by {@link #getViewDescriptors()}. */ public static final String VIEW_MERGE = "merge"; //$NON-NLS-1$ /** + * The XML name of the special {@code <requestFocus>} layout tag. + * A synthetic element with that name is created as part of the view descriptors list + * returned by {@link #getViewDescriptors()}. + */ + public static final String REQUEST_FOCUS = "requestFocus";//$NON-NLS-1$ + + /** * The attribute name of the include tag's url naming the resource to be inserted * <p> * <b>NOTE</b>: The layout attribute is NOT in the Android namespace! @@ -174,6 +181,10 @@ public final class LayoutDescriptors implements IDescriptorProvider { fixSuperClasses(infoDescMap); + ViewElementDescriptor requestFocus = createRequestFocus(); + newViews.add(requestFocus); + newDescriptors.add(requestFocus); + // The <merge> tag can only be a root tag, so it is added at the end. // It gets everything else as children but it is not made a child itself. ViewElementDescriptor mergeTag = createMerge(newLayouts); @@ -342,7 +353,7 @@ public final class LayoutDescriptors implements IDescriptorProvider { } /** - * Creates and return a new <merge> descriptor. + * Creates and return a new {@code <merge>} descriptor. * @param knownLayouts A list of all known layout view descriptors, used to find the * FrameLayout descriptor and extract its layout attributes. */ @@ -368,6 +379,27 @@ public final class LayoutDescriptors implements IDescriptorProvider { } /** + * Creates and return a new {@code <requestFocus>} descriptor. + * @param knownLayouts A list of all known layout view descriptors, used to find the + * FrameLayout descriptor and extract its layout attributes. + */ + private ViewElementDescriptor createRequestFocus() { + String xml_name = REQUEST_FOCUS; + + // Create the include descriptor + return new ViewElementDescriptor( + xml_name, // xml_name + xml_name, // ui_name + xml_name, // "class name"; the GLE only treats this as an element tag + "Requests focus for the parent element or one of its descendants", // tooltip + null, // sdk_url + null, // attributes + null, // layout attributes + null, // children + false /* mandatory */); + } + + /** * Finds the descriptor and retrieves all its layout attributes. */ private AttributeDescriptor[] findViewLayoutAttributes( diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java index ca07b7a..b45a197 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java @@ -16,11 +16,15 @@ package com.android.ide.eclipse.adt.internal.editors.layout.descriptors; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import org.eclipse.swt.graphics.Image; + /** * {@link ViewElementDescriptor} describes the properties expected for a given XML element node * representing a class in an XML Layout file. @@ -43,13 +47,13 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; * * @see ElementDescriptor */ -public final class ViewElementDescriptor extends ElementDescriptor { +public class ViewElementDescriptor extends ElementDescriptor { /** The full class name (FQCN) of this view. */ - private String mFullClassName; + private final String mFullClassName; /** The list of layout attributes. Can be empty but not null. */ - private AttributeDescriptor[] mLayoutAttributes; + private final AttributeDescriptor[] mLayoutAttributes; /** The super-class descriptor. Can be null. */ private ViewElementDescriptor mSuperClassDesc; @@ -96,6 +100,7 @@ public final class ViewElementDescriptor extends ElementDescriptor { public ViewElementDescriptor(String xml_name, String fullClassName) { super(xml_name); mFullClassName = fullClassName; + mLayoutAttributes = null; } /** @@ -134,4 +139,33 @@ public final class ViewElementDescriptor extends ElementDescriptor { public void setSuperClass(ViewElementDescriptor superClassDesc) { mSuperClassDesc = superClassDesc; } + + /** + * Returns an optional icon for the element. + * <p/> + * By default this tries to return an icon based on the XML name of the element. + * If this fails, it tries to return the default element icon as defined in the + * plugin. If all fails, it returns null. + * + * @return An icon for this element or null. + */ + @Override + public Image getGenericIcon() { + IconFactory factory = IconFactory.getInstance(); + String name = mXmlName; + if (name.indexOf('.') != -1) { + // If the user uses a fully qualified name, such as + // "android.gesture.GestureOverlayView" in their XML, we need to look up + // only by basename + name = name.substring(name.lastIndexOf('.') + 1); + } + + Image icon = factory.getIcon(name); + if (icon == null) { + icon = AdtPlugin.getAndroidLogo(); + } + + return icon; + } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java index 24c582d..3c18b16 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java @@ -82,8 +82,8 @@ public abstract class AccordionControl extends Composite { * overridden to lay out the children with a different layout than the default * vertical RowLayout */ - protected Composite createChildContainer(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); + protected Composite createChildContainer(Composite parent, Object header, int style) { + Composite composite = new Composite(parent, style); if (mWrap) { RowLayout layout = new RowLayout(SWT.HORIZONTAL); layout.center = true; @@ -335,9 +335,12 @@ public abstract class AccordionControl extends Composite { updateIcon(label); if (!scrollGridData.exclude && scrolledComposite.getContent() == null) { - Composite composite = createChildContainer(scrolledComposite); Object header = getHeader(label); + Composite composite = createChildContainer(scrolledComposite, header, SWT.NONE); createChildren(composite, header); + while (composite.getParent() != scrolledComposite) { + composite = composite.getParent(); + } scrolledComposite.setContent(composite); scrolledComposite.setMinSize(composite.computeSize(SWT.DEFAULT, SWT.DEFAULT)); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java index 6c32445..2a70dd0 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfo.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW; import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE; import com.android.ide.common.api.Rect; @@ -352,7 +353,7 @@ public class CanvasViewInfo implements IPropertySource { // root as well (such that the whole layout canvas does not highlight as part of hovers // etc) if (mParent != null - && mParent.mName.endsWith("GestureOverlayView") //$NON-NLS-1$ + && mParent.mName.endsWith(GESTURE_OVERLAY_VIEW) && mParent.isRoot()) { return true; } @@ -362,16 +363,16 @@ public class CanvasViewInfo implements IPropertySource { } /** - * Returns true if this {@link CanvasViewInfo} represents an invisible parent - in - * other words, a view that can have children, and that has zero bounds making it - * effectively invisible. (We don't actually look for -0- bounds, but - * bounds smaller than SELECTION_MIN_SIZE.) + * Returns true if this {@link CanvasViewInfo} represents an invisible widget that + * should be highlighted when selected. This is the case for any layout that is less than the minimum + * threshold ({@link #SELECTION_MIN_SIZE}), or any other view that has -0- bounds. * - * @return True if this is an invisible parent. + * @return True if this is a tiny layout or invisible view */ - public boolean isInvisibleParent() { + public boolean isInvisible() { if (mAbsRect.width < SELECTION_MIN_SIZE || mAbsRect.height < SELECTION_MIN_SIZE) { - return mUiViewNode != null && mUiViewNode.getDescriptor().hasChildren(); + return mUiViewNode != null && (mUiViewNode.getDescriptor().hasChildren() || + mAbsRect.width <= 0 || mAbsRect.height <= 0); } return false; @@ -382,7 +383,7 @@ public class CanvasViewInfo implements IPropertySource { * make it visible during selection or dragging? Note that this is NOT considered to * be the case in the explode-all-views mode where all nodes have their padding * increased; it's only used for views that individually exploded because they were - * requested visible and they returned true for {@link #isInvisibleParent()}. + * requested visible and they returned true for {@link #isInvisible()}. * * @return True if this is an exploded node. */ @@ -525,19 +526,41 @@ public class CanvasViewInfo implements IPropertySource { * This method will build up a set of {@link CanvasViewInfo} that corresponds to the * actual <b>selectable</b> views (which are also shown in the Outline). * + * @param layoutlib5 if true, the {@link ViewInfo} hierarchy was created by layoutlib + * version 5 or higher, which means this algorithm can make certain assumptions + * (for example that {@code <merge>} siblings will provide {@link MergeCookie} + * references, so we don't have to search for them.) * @param root the root {@link ViewInfo} to build from * @return a {@link CanvasViewInfo} hierarchy */ - public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { - return new Builder().create(root); + public static Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root, boolean layoutlib5) { + return new Builder(layoutlib5).create(root); } /** Builder object which walks over a tree of {@link ViewInfo} objects and builds * up a corresponding {@link CanvasViewInfo} hierarchy. */ private static class Builder { - private Map<UiViewElementNode,List<CanvasViewInfo>> mMergeNodeMap; + public Builder(boolean layoutlib5) { + mLayoutLib5 = layoutlib5; + } - public Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { + /** + * The mapping from nodes that have a {@code <merge>} as a parent in the node + * model to their corresponding views + */ + private Map<UiViewElementNode, List<CanvasViewInfo>> mMergeNodeMap; + + /** + * Whether the ViewInfos are provided by a layout library that is version 5 or + * later, since that will allow us to take several shortcuts + */ + private boolean mLayoutLib5; + + /** + * Creates a hierarchy of {@link CanvasViewInfo} objects and merge bounding + * rectangles from the given {@link ViewInfo} hierarchy + */ + private Pair<CanvasViewInfo,List<Rectangle>> create(ViewInfo root) { Object cookie = root.getCookie(); if (cookie == null) { // Special case: If the root-most view does not have a view cookie, @@ -716,10 +739,24 @@ public class CanvasViewInfo implements IPropertySource { parentX += viewInfo.getLeft(); parentY += viewInfo.getTop(); + List<ViewInfo> children = viewInfo.getChildren(); + + if (mLayoutLib5) { + for (ViewInfo child : children) { + Object cookie = child.getCookie(); + if (cookie instanceof UiViewElementNode || cookie instanceof MergeCookie) { + CanvasViewInfo childView = createSubtree(view, child, + parentX, parentY); + view.addChild(childView); + } // else: null cookies, adapter item references, etc: No child views. + } + + return view; + } + // See if we have any missing keys at this level int missingNodes = 0; int mergeNodes = 0; - List<ViewInfo> children = viewInfo.getChildren(); for (ViewInfo child : children) { // Only use children which have a ViewKey of the correct type. // We can't interact with those when they have a null key or @@ -750,7 +787,9 @@ public class CanvasViewInfo implements IPropertySource { // embedded_layout rendering, or we are including a view with a <merge> // as the root element. - String containerName = view.getUiViewNode().getDescriptor().getXmlLocalName(); + UiViewElementNode uiViewNode = view.getUiViewNode(); + String containerName = uiViewNode != null + ? uiViewNode.getDescriptor().getXmlLocalName() : ""; //$NON-NLS-1$ if (containerName.equals(LayoutDescriptors.VIEW_INCLUDE)) { // This is expected -- we don't WANT to get node keys for the content // of an include since it's in a different file and should be treated @@ -763,9 +802,11 @@ public class CanvasViewInfo implements IPropertySource { // that there are <merge> tags which are doing surprising things // to the view hierarchy LinkedList<UiViewElementNode> unused = new LinkedList<UiViewElementNode>(); - for (UiElementNode child : view.getUiViewNode().getUiChildren()) { - if (child instanceof UiViewElementNode) { - unused.addLast((UiViewElementNode) child); + if (uiViewNode != null) { + for (UiElementNode child : uiViewNode.getUiChildren()) { + if (child instanceof UiViewElementNode) { + unused.addLast((UiViewElementNode) child); + } } } for (ViewInfo child : children) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java index e90371f..ba09220 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ControlPoint.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; @@ -57,6 +58,20 @@ public final class ControlPoint { } /** + * Constructs a new {@link ControlPoint} from the given menu detect event. + * + * @param canvas The {@link LayoutCanvas} this point is within. + * @param event The menu detect event to construct the {@link ControlPoint} from. + * @return A {@link ControlPoint} which corresponds to the given + * {@link MenuDetectEvent}. + */ + public static ControlPoint create(LayoutCanvas canvas, MenuDetectEvent event) { + // The menu detect events are always display-relative. + org.eclipse.swt.graphics.Point p = canvas.toControl(event.x, event.y); + return new ControlPoint(canvas, p.x, p.y); + } + + /** * Constructs a new {@link ControlPoint} from the given event. The event * must be from a {@link DragSourceListener} associated with the * {@link LayoutCanvas} such that the {@link DragSourceEvent#x} and diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java new file mode 100644 index 0000000..3226a02 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CustomViewFinder.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import static com.android.sdklib.SdkConstants.CLASS_VIEW; +import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP; +import static com.android.sdklib.SdkConstants.FN_FRAMEWORK_LIBRARY; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.util.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchParticipant; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.SearchRequestor; +import org.eclipse.jdt.internal.core.ResolvedBinaryType; +import org.eclipse.jdt.internal.core.ResolvedSourceType; +import org.eclipse.swt.widgets.Display; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * The {@link CustomViewFinder} can look up the custom views and third party views + * available for a given project. + */ +@SuppressWarnings("restriction") // JDT model access for custom-view class lookup +public class CustomViewFinder { + /** + * Qualified name for the per-project non-persistent property storing the + * {@link CustomViewFinder} for this project + */ + private final static QualifiedName CUSTOM_VIEW_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID, + "viewfinder"); //$NON-NLS-1$ + + /** Project that this view finder locates views for */ + private final IProject mProject; + + private final List<Listener> mListeners = new ArrayList<Listener>(); + + private List<String> mCustomViews; + private List<String> mThirdPartyViews; + private boolean mRefreshing; + + /** + * Constructs an {@link CustomViewFinder} for the given project. Don't use this method; + * use the {@link #get} factory method instead. + * + * @param project project to create an {@link CustomViewFinder} for + */ + private CustomViewFinder(IProject project) { + mProject = project; + } + + /** + * Returns the {@link CustomViewFinder} for the given project + * + * @param project the project the finder is associated with + * @return a {@CustomViewFinder} for the given project, never null + */ + public static CustomViewFinder get(IProject project) { + CustomViewFinder finder = null; + try { + finder = (CustomViewFinder) project.getSessionProperty(CUSTOM_VIEW_FINDER); + } catch (CoreException e) { + // Not a problem; we will just create a new one + } + + if (finder == null) { + finder = new CustomViewFinder(project); + try { + project.setSessionProperty(CUSTOM_VIEW_FINDER, finder); + } catch (CoreException e) { + AdtPlugin.log(e, "Can't store CustomViewFinder"); + } + } + + return finder; + } + + public void refresh() { + refresh(null); + } + + public void refresh(final Listener listener) { + // Add this listener to the list of listeners which should be notified when the + // search is done. (There could be more than one since multiple requests could + // arrive for a slow search since the search is run in a different thread). + if (listener != null) { + synchronized (this) { + mListeners.add(listener); + } + } + synchronized (this) { + if (listener != null) { + mListeners.add(listener); + } + if (mRefreshing) { + return; + } + mRefreshing = true; + } + + FindViewsJob job = new FindViewsJob(); + job.schedule(); + } + + public Collection<String> getCustomViews() { + return mCustomViews == null ? null : Collections.unmodifiableCollection(mCustomViews); + } + + public Collection<String> getThirdPartyViews() { + return mThirdPartyViews == null + ? null : Collections.unmodifiableCollection(mThirdPartyViews); + } + + public Collection<String> getAllViews() { + // Not yet initialized: return null + if (mCustomViews == null) { + return null; + } + List<String> all = new ArrayList<String>(mCustomViews.size() + mThirdPartyViews.size()); + all.addAll(mCustomViews); + all.addAll(mThirdPartyViews); + return all; + } + + /** + * Returns a pair of view lists - the custom views and the 3rd-party views. + * This method performs no caching; it is the same as asking the custom view finder + * to refresh itself and then waiting for the answer and returning it. + * + * @param project the Android project + * @param layoutsOnly if true, only search for layouts + * @return a pair of lists, the first containing custom views and the second + * containing 3rd party views + */ + public static Pair<List<String>,List<String>> findViews( + final IProject project, boolean layoutsOnly) { + CustomViewFinder finder = get(project); + + return finder.findViews(layoutsOnly); + } + + private Pair<List<String>,List<String>> findViews(final boolean layoutsOnly) { + final List<String> customViews = new ArrayList<String>(); + final List<String> thirdPartyViews = new ArrayList<String>(); + + ProjectState state = Sdk.getProjectState(mProject); + final List<IProject> libraries = state != null + ? state.getFullLibraryProjects() : Collections.<IProject>emptyList(); + + SearchRequestor requestor = new SearchRequestor() { + @Override + public void acceptSearchMatch(SearchMatch match) throws CoreException { + // Ignore matches in comments + if (match.isInsideDocComment()) { + return; + } + + Object element = match.getElement(); + if (element instanceof ResolvedBinaryType) { + // Third party view + ResolvedBinaryType type = (ResolvedBinaryType) element; + IPackageFragment fragment = type.getPackageFragment(); + IPath path = fragment.getPath(); + String last = path.lastSegment(); + // Filter out android.jar stuff + if (last.equals(FN_FRAMEWORK_LIBRARY)) { + return; + } + if (!isValidView(type, layoutsOnly)) { + return; + } + + IProject matchProject = match.getResource().getProject(); + if (mProject == matchProject || libraries.contains(matchProject)) { + String fqn = type.getFullyQualifiedName(); + thirdPartyViews.add(fqn); + } + } else if (element instanceof ResolvedSourceType) { + // User custom view + IProject matchProject = match.getResource().getProject(); + if (mProject == matchProject || libraries.contains(matchProject)) { + ResolvedSourceType type = (ResolvedSourceType) element; + if (!isValidView(type, layoutsOnly)) { + return; + } + String fqn = type.getFullyQualifiedName(); + fqn = fqn.replace('$', '.'); + customViews.add(fqn); + } + } + } + }; + try { + IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject); + if (javaProject != null) { + String className = layoutsOnly ? CLASS_VIEWGROUP : CLASS_VIEW; + IType viewType = javaProject.findType(className); + if (viewType != null) { + IJavaSearchScope scope = SearchEngine.createHierarchyScope(viewType); + SearchParticipant[] participants = new SearchParticipant[] { + SearchEngine.getDefaultSearchParticipant() + }; + int matchRule = SearchPattern.R_PATTERN_MATCH | SearchPattern.R_CASE_SENSITIVE; + + SearchPattern pattern = SearchPattern.createPattern("*", + IJavaSearchConstants.CLASS, IJavaSearchConstants.IMPLEMENTORS, + matchRule); + SearchEngine engine = new SearchEngine(); + engine.search(pattern, participants, scope, requestor, + new NullProgressMonitor()); + } + } + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + if (!layoutsOnly) { + // Update our cached answers (unless we were filtered on only layouts) + mCustomViews = customViews; + mThirdPartyViews = thirdPartyViews; + } + + return Pair.of(customViews, thirdPartyViews); + } + + /** + * Determines whether the given member is a valid android.view.View to be added to the + * list of custom views or third party views. It checks that the view is public and + * not abstract for example. + */ + private static boolean isValidView(IType type, boolean layoutsOnly) + throws JavaModelException { + // Skip anonymous classes + if (type.isAnonymous()) { + return false; + } + int flags = type.getFlags(); + if (Flags.isAbstract(flags) || !Flags.isPublic(flags)) { + return false; + } + + // TODO: if (layoutsOnly) perhaps try to filter out AdapterViews and other ViewGroups + // not willing to accept children via XML + + // See if the class has one of the acceptable constructors + // needed for XML instantiation: + // View(Context context) + // View(Context context, AttributeSet attrs) + // View(Context context, AttributeSet attrs, int defStyle) + // We don't simply do three direct checks via type.getMethod() because the types + // are not resolved, so we don't know for each parameter if we will get the + // fully qualified or the unqualified class names. + // Instead, iterate over the methods and look for a match. + String typeName = type.getElementName(); + for (IMethod method : type.getMethods()) { + // Only care about constructors + if (!method.getElementName().equals(typeName)) { + continue; + } + + String[] parameterTypes = method.getParameterTypes(); + if (parameterTypes == null || parameterTypes.length < 1 || parameterTypes.length > 3) { + continue; + } + + String first = parameterTypes[0]; + // Look for the parameter type signatures -- produced by + // JDT's Signature.createTypeSignature("Context", false /*isResolved*/);. + // This is not a typo; they were copy/pasted from the actual parameter names + // observed in the debugger examining these data structures. + if (first.equals("QContext;") //$NON-NLS-1$ + || first.equals("Qandroid.content.Context;")) { //$NON-NLS-1$ + if (parameterTypes.length == 1) { + return true; + } + String second = parameterTypes[1]; + if (second.equals("QAttributeSet;") //$NON-NLS-1$ + || second.equals("Qandroid.util.AttributeSet;")) { //$NON-NLS-1$ + if (parameterTypes.length == 2) { + return true; + } + String third = parameterTypes[2]; + if (third.equals("I")) { //$NON-NLS-1$ + if (parameterTypes.length == 3) { + return true; + } + } + } + } + } + + return false; + } + + /** + * Interface implemented by clients of the {@link CustomViewFinder} to be notified + * when a custom view search has completed. Will always be called on the SWT event + * dispatch thread. + */ + public interface Listener { + void viewsUpdated(Collection<String> customViews, Collection<String> thirdPartyViews); + } + + /** + * Job for performing class search off the UI thread. This is marked as a system job + * so that it won't show up in the progress monitor etc. + */ + private class FindViewsJob extends Job { + FindViewsJob() { + super("Find Custom Views"); + setSystem(true); + } + @Override + protected IStatus run(IProgressMonitor monitor) { + Pair<List<String>, List<String>> views = findViews(false); + mCustomViews = views.getFirst(); + mThirdPartyViews = views.getSecond(); + + // Notify listeners on SWT's UI thread + Display.getDefault().asyncExec(new Runnable() { + public void run() { + Collection<String> customViews = + Collections.unmodifiableCollection(mCustomViews); + Collection<String> thirdPartyViews = + Collections.unmodifiableCollection(mThirdPartyViews); + synchronized (this) { + for (Listener l : mListeners) { + l.viewsUpdated(customViews, thirdPartyViews); + } + mListeners.clear(); + mRefreshing = false; + } + } + }); + return Status.OK_STATUS; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java new file mode 100644 index 0000000..64ea86a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; + +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; +import com.android.util.Pair; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; +import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@SuppressWarnings("restriction") // No replacement for restricted XML model yet +public class DomUtilities { + /** + * Returns the XML DOM node corresponding to the given offset of the given + * document. + * + * @param document The document to look in + * @param offset The offset to look up the node for + * @return The node containing the offset, or null + */ + public static Node getNode(IDocument document, int offset) { + Node node = null; + IModelManager modelManager = StructuredModelManager.getModelManager(); + if (modelManager == null) { + return null; + } + try { + IStructuredModel model = modelManager.getExistingModelForRead(document); + if (model != null) { + try { + for (; offset >= 0 && node == null; --offset) { + node = (Node) model.getIndexedRegion(offset); + } + } finally { + model.releaseFromRead(); + } + } + } catch (Exception e) { + // Ignore exceptions. + } + + return node; + } + + /** + * Returns the editing context at the given offset, as a pair of parent node and child + * node. This is not the same as just calling {@link DomUtilities#getNode} and taking + * its parent node, because special care has to be taken to return content element + * positions. + * <p> + * For example, for the XML {@code <foo>^</foo>}, if the caret ^ is inside the foo + * element, between the opening and closing tags, then the foo element is the parent, + * and the child is null which represents a potential text node. + * <p> + * If the node is inside an element tag definition (between the opening and closing + * bracket) then the child node will be the element and whatever parent (element or + * document) will be its parent. + * <p> + * If the node is in a text node, then the text node will be the child and its parent + * element or document node its parent. + * <p> + * Finally, if the caret is on a boundary of a text node, then the text node will be + * considered the child, regardless of whether it is on the left or right of the + * caret. For example, in the XML {@code <foo>^ </foo>} and in the XML + * {@code <foo> ^</foo>}, in both cases the text node is preferred over the element. + * + * @param document the document to search in + * @param offset the offset to look up + * @return a pair of parent and child elements, where either the parent or the child + * but not both can be null, and if non null the child.getParentNode() should + * return the parent. Note that the method can also return null if no + * document or model could be obtained or if the offset is invalid. + */ + public static Pair<Node, Node> getNodeContext(IDocument document, int offset) { + Node node = null; + IModelManager modelManager = StructuredModelManager.getModelManager(); + if (modelManager == null) { + return null; + } + try { + IStructuredModel model = modelManager.getExistingModelForRead(document); + if (model != null) { + try { + for (; offset >= 0 && node == null; --offset) { + IndexedRegion indexedRegion = model.getIndexedRegion(offset); + if (indexedRegion != null) { + node = (Node) indexedRegion; + + if (node.getNodeType() == Node.TEXT_NODE) { + return Pair.of(node.getParentNode(), node); + } + + // Look at the structured document to see if + // we have the special case where the caret is pointing at + // a -potential- text node, e.g. <foo>^</foo> + IStructuredDocument doc = model.getStructuredDocument(); + IStructuredDocumentRegion region = + doc.getRegionAtCharacterOffset(offset); + + ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); + String type = subRegion.getType(); + if (DOMRegionContext.XML_END_TAG_OPEN.equals(type)) { + // Try to return the text node if it's on the left + // of this element node, such that replace strings etc + // can be computed. + Node lastChild = node.getLastChild(); + if (lastChild != null) { + IndexedRegion previousRegion = (IndexedRegion) lastChild; + if (previousRegion.getEndOffset() == offset) { + return Pair.of(node, lastChild); + } + } + return Pair.of(node, null); + } + + return Pair.of(node.getParentNode(), node); + } + } + } finally { + model.releaseFromRead(); + } + } + } catch (Exception e) { + // Ignore exceptions. + } + + return null; + } + + /** + * Like {@link #getNode(IDocument, int)}, but has a bias parameter which lets you + * indicate whether you want the search to look forwards or backwards. + * This is vital when trying to compute a node range. Consider the following + * XML fragment: + * {@code + * <a/><b/>[<c/><d/><e/>]<f/><g/> + * } + * Suppose we want to locate the nodes in the range indicated by the brackets above. + * If we want to search for the node corresponding to the start position, should + * we pick the node on its left or the node on its right? Similarly for the end + * position. Clearly, we'll need to bias the search towards the right when looking + * for the start position, and towards the left when looking for the end position. + * The following method lets us do just that. When passed an offset which sits + * on the edge of the computed node, it will pick the neighbor based on whether + * "forward" is true or false, where forward means searching towards the right + * and not forward is obviously towards the left. + * @param document the document to search in + * @param offset the offset to search for + * @param forward if true, search forwards, otherwise search backwards when on node boundaries + * @return the node which surrounds the given offset, or the node adjacent to the offset + * where the side depends on the forward parameter + */ + public static Node getNode(IDocument document, int offset, boolean forward) { + Node node = getNode(document, offset); + + if (node instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) node; + + if (!forward && offset <= region.getStartOffset()) { + Node left = node.getPreviousSibling(); + if (left == null) { + left = node.getParentNode(); + } + + node = left; + } else if (forward && offset >= region.getEndOffset()) { + Node right = node.getNextSibling(); + if (right == null) { + right = node.getParentNode(); + } + node = right; + } + } + + return node; + } + + /** + * Returns a range of elements for the given caret range. Note that the two elements + * may not be at the same level so callers may want to perform additional input + * filtering. + * + * @param document the document to search in + * @param beginOffset the beginning offset of the range + * @param endOffset the ending offset of the range + * @return a pair of begin+end elements, or null + */ + public static Pair<Element, Element> getElementRange(IDocument document, int beginOffset, + int endOffset) { + Element beginElement = null; + Element endElement = null; + Node beginNode = getNode(document, beginOffset, true); + Node endNode = beginNode; + if (endOffset > beginOffset) { + endNode = getNode(document, endOffset, false); + } + + if (beginNode == null || endNode == null) { + return null; + } + + // Adjust offsets if you're pointing at text + if (beginNode.getNodeType() != Node.ELEMENT_NODE) { + // <foo> <bar1/> | <bar2/> </foo> => should pick <bar2/> + beginElement = getNextElement(beginNode); + if (beginElement == null) { + // Might be inside the end of a parent, e.g. + // <foo> <bar/> | </foo> => should pick <bar/> + beginElement = getPreviousElement(beginNode); + if (beginElement == null) { + // We must be inside an empty element, + // <foo> | </foo> + // In that case just pick the parent. + beginElement = getParentElement(beginNode); + } + } + } else { + beginElement = (Element) beginNode; + } + + if (endNode.getNodeType() != Node.ELEMENT_NODE) { + // In the following, | marks the caret position: + // <foo> <bar1/> | <bar2/> </foo> => should pick <bar1/> + endElement = getPreviousElement(endNode); + if (endElement == null) { + // Might be inside the beginning of a parent, e.g. + // <foo> | <bar/></foo> => should pick <bar/> + endElement = getNextElement(endNode); + if (endElement == null) { + // We must be inside an empty element, + // <foo> | </foo> + // In that case just pick the parent. + endElement = getParentElement(endNode); + } + } + } else { + endElement = (Element) endNode; + } + + if (beginElement != null && endElement != null) { + return Pair.of(beginElement, endElement); + } + + return null; + } + + /** + * Returns the next sibling element of the node, or null if there is no such element + * + * @param node the starting node + * @return the next sibling element, or null + */ + public static Element getNextElement(Node node) { + while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { + node = node.getNextSibling(); + } + + return (Element) node; // may be null as well + } + + /** + * Returns the previous sibling element of the node, or null if there is no such element + * + * @param node the starting node + * @return the previous sibling element, or null + */ + public static Element getPreviousElement(Node node) { + while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { + node = node.getPreviousSibling(); + } + + return (Element) node; // may be null as well + } + + /** + * Returns the closest ancestor element, or null if none + * + * @param node the starting node + * @return the closest parent element, or null + */ + public static Element getParentElement(Node node) { + while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { + node = node.getParentNode(); + } + + return (Element) node; // may be null as well + } + + /** + * Converts the given attribute value to an XML-attribute-safe value, meaning that + * single and double quotes are replaced with their corresponding XML entities. + * + * @param attrValue the value to be escaped + * @return the escaped value + */ + public static String toXmlAttributeValue(String attrValue) { + // Must escape ' and " + if (attrValue.indexOf('"') == -1 && attrValue.indexOf('\'') == -1) { + return attrValue; + } + + int n = attrValue.length(); + StringBuilder sb = new StringBuilder(2 * n); + for (int i = 0; i < n; i++) { + char c = attrValue.charAt(i); + if (c == '"') { + sb.append("""); //$NON-NLS-1$ + } else if (c == '\'') { + sb.append("'"); //$NON-NLS-1$ + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** Utility used by {@link #getFreeWidgetId(Element)} */ + private static void addLowercaseIds(Element root, Set<String> seen) { + if (root.hasAttributeNS(ANDROID_URI, ATTR_ID)) { + String id = root.getAttributeNS(ANDROID_URI, ATTR_ID); + if (id.startsWith(NEW_ID_PREFIX)) { + seen.add(id.substring(NEW_ID_PREFIX.length()).toLowerCase()); + } else if (id.startsWith(ID_PREFIX)) { + seen.add(id.substring(ID_PREFIX.length()).toLowerCase()); + } else { + seen.add(id.toLowerCase()); + } + } + } + + /** + * Returns a suitable new widget id (not including the {@code @id/} prefix) for the + * given element, which is guaranteed to be unique in this document + * + * @param element the element to compute a new widget id for + * @param reserved an optional set of extra, "reserved" set of ids that should be + * considered taken + * @param prefix an optional prefix to use for the generated name, or null to get a + * default (which is currently the tag name) + * @return a unique id, never null, which does not include the {@code @id/} prefix + * @see DescriptorsUtils#getFreeWidgetId + */ + public static String getFreeWidgetId(Element element, Set<String> reserved, String prefix) { + Set<String> ids = new HashSet<String>(); + if (reserved != null) { + for (String id : reserved) { + ids.add(id.toLowerCase()); + } + } + addLowercaseIds(element.getOwnerDocument().getDocumentElement(), ids); + + if (prefix == null) { + prefix = DescriptorsUtils.getBasename(element.getTagName()); + } + String generated; + int num = 1; + do { + generated = String.format("%1$s%2$d", prefix, num++); //$NON-NLS-1$ + } while (ids.contains(generated.toLowerCase())); + + return generated; + } + + /** + * Returns the element children of the given element + * + * @param element the parent element + * @return a list of child elements, possibly empty but never null + */ + public static List<Element> getChildren(Element element) { + // Convenience to avoid lots of ugly DOM access casting + NodeList children = element.getChildNodes(); + // An iterator would have been more natural (to directly drive the child list + // iteration) but iterators can't be used in enhanced for loops... + List<Element> result = new ArrayList<Element>(children.getLength()); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + result.add(child); + } + } + + return result; + } + + /** + * Returns true iff the given elements are contiguous siblings + * + * @param elements the elements to be tested + * @return true if the elements are contiguous siblings with no gaps + */ + public static boolean isContiguous(List<Element> elements) { + if (elements.size() > 1) { + // All elements must be siblings (e.g. same parent) + Node parent = elements.get(0).getParentNode(); + if (!(parent instanceof Element)) { + return false; + } + for (Element node : elements) { + if (parent != node.getParentNode()) { + return false; + } + } + + // Ensure that the siblings are contiguous; no gaps. + // If we've selected all the children of the parent then we don't need + // to look. + List<Element> siblings = DomUtilities.getChildren((Element) parent); + if (siblings.size() != elements.size()) { + Set<Element> nodeSet = new HashSet<Element>(elements); + boolean inRange = false; + int remaining = elements.size(); + for (Element node : siblings) { + boolean in = nodeSet.contains(node); + if (in) { + remaining--; + if (remaining == 0) { + break; + } + inRange = true; + } else if (inRange) { + return false; + } + } + } + } + + return true; + } + + /** + * Determines whether two element trees are equivalent. Two element trees are + * equivalent if they represent the same DOM structure (elements, attributes, and + * children in order). This is almost the same as simply checking whether the String + * representations of the two nodes are identical, but this allows for minor + * variations that are not semantically significant, such as variations in formatting + * or ordering of the element attribute declarations, and the text children are + * ignored (this is such that in for example layout where content is only used for + * indentation the indentation differences are ignored). Null trees are never equal. + * + * @param element1 the first element to compare + * @param element2 the second element to compare + * @return true if the two element hierarchies are logically equal + */ + public static boolean isEquivalent(Element element1, Element element2) { + if (element1 == null || element2 == null) { + return false; + } + + if (!element1.getTagName().equals(element2.getTagName())) { + return false; + } + + // Check attribute map + NamedNodeMap attributes1 = element1.getAttributes(); + NamedNodeMap attributes2 = element2.getAttributes(); + if (attributes1.getLength() != attributes2.getLength()) { + return false; + } + if (attributes1.getLength() > 0) { + List<Attr> attributeNodes1 = new ArrayList<Attr>(); + for (int i = 0, n = attributes1.getLength(); i < n; i++) { + attributeNodes1.add((Attr) attributes1.item(i)); + } + List<Attr> attributeNodes2 = new ArrayList<Attr>(); + for (int i = 0, n = attributes2.getLength(); i < n; i++) { + attributeNodes2.add((Attr) attributes2.item(i)); + } + Collections.sort(attributeNodes1, ATTRIBUTE_COMPARATOR); + Collections.sort(attributeNodes2, ATTRIBUTE_COMPARATOR); + for (int i = 0; i < attributeNodes1.size(); i++) { + Attr attr1 = attributeNodes1.get(i); + Attr attr2 = attributeNodes2.get(i); + if (attr1.getLocalName() == null || attr2.getLocalName() == null) { + if (!attr1.getName().equals(attr2.getName())) { + return false; + } + } else if (!attr1.getLocalName().equals(attr2.getLocalName())) { + return false; + } + if (!attr1.getValue().equals(attr2.getValue())) { + return false; + } + if (attr1.getNamespaceURI() == null) { + if (attr2.getNamespaceURI() != null) { + return false; + } + } else if (attr2.getNamespaceURI() == null) { + return false; + } else if (!attr1.getNamespaceURI().equals(attr2.getNamespaceURI())) { + return false; + } + } + } + + NodeList children1 = element1.getChildNodes(); + NodeList children2 = element2.getChildNodes(); + int nextIndex1 = 0; + int nextIndex2 = 0; + while (true) { + while (nextIndex1 < children1.getLength() && + children1.item(nextIndex1).getNodeType() != Node.ELEMENT_NODE) { + nextIndex1++; + } + + while (nextIndex2 < children2.getLength() && + children2.item(nextIndex2).getNodeType() != Node.ELEMENT_NODE) { + nextIndex2++; + } + + Element nextElement1 = (Element) (nextIndex1 < children1.getLength() + ? children1.item(nextIndex1) : null); + Element nextElement2 = (Element) (nextIndex2 < children2.getLength() + ? children2.item(nextIndex2) : null); + if (nextElement1 == null) { + if (nextElement2 == null) { + return true; + } else { + return false; + } + } else if (nextElement2 == null) { + return false; + } else if (!isEquivalent(nextElement1, nextElement2)) { + return false; + } + nextIndex1++; + nextIndex2++; + } + } + + /** + * Finds the corresponding element in a document to a given element in another + * document. Note that this does <b>not</b> do any kind of equivalence check + * (see {@link #isEquivalent(Element, Element)}), and currently the search + * is only by id; there is no structural search. + * + * @param element the element to find an equivalent for + * @param document the document to search for an equivalent element in + * @return an equivalent element, or null + */ + public static Element findCorresponding(Element element, Document document) { + // Make sure the method is called correctly -- the element is for a different + // document than the one we are searching + assert element.getOwnerDocument() != document; + + // First search by id. This allows us to find the corresponding + String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); + if (id != null && id.length() > 0) { + if (id.startsWith(ID_PREFIX)) { + id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length()); + } + + return findCorresponding(document.getDocumentElement(), id); + } + + // TODO: Search by structure - look in the document and + // find a corresponding element in the same location in the structure, + // e.g. 4th child of root, 3rd child, 6th child, then pick node with tag "foo". + + return null; + } + + /** Helper method for {@link #findCorresponding(Element, Document)} */ + private static Element findCorresponding(Element element, String targetId) { + String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); + if (id != null) { // Work around DOM bug + if (id.equals(targetId)) { + return element; + } else if (id.startsWith(ID_PREFIX)) { + id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length()); + if (id.equals(targetId)) { + return element; + } + } + } + + NodeList children = element.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + Element match = findCorresponding(child, targetId); + if (match != null) { + return match; + } + } + } + + return null; + } + + /** Can be used to sort attributes by name */ + private static final Comparator<Attr> ATTRIBUTE_COMPARATOR = new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + return a1.getName().compareTo(a2.getName()); + } + }; +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java index 5464917..f188e90 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java @@ -16,6 +16,10 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.EXPANDABLE_LIST_VIEW; +import static com.android.ide.common.layout.LayoutConstants.FQCN_GESTURE_OVERLAY_VIEW; +import static com.android.ide.common.layout.LayoutConstants.LIST_VIEW; + import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.MenuAction; @@ -23,6 +27,12 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutAction; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeAction; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; @@ -37,8 +47,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.TreeMap; +import java.util.Map.Entry; import java.util.regex.Pattern; /** @@ -200,18 +210,46 @@ import java.util.regex.Pattern; } } - insertExtractAsInclude(endId); + insertListPreviewType(endId); + insertVisualRefactorings(endId); } - private void insertExtractAsInclude(String endId) { - // Extract As <include> refactoring. + private void insertVisualRefactorings(String endId) { + // Extract As <include> refactoring, Wrap In Refactoring, etc. + List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); + if (selection.size() == 0) { + return; + } // Only include the menu item if you are not right clicking on a root, // or on an included view, or on a non-contiguous selection - IAction extractIncludeAction = new ExtractIncludeAction(mCanvas); - if (extractIncludeAction.isEnabled()) { - mMenuManager.insertBefore(endId, new Separator()); - mMenuManager.insertBefore(endId, extractIncludeAction); - mMenuManager.insertBefore(endId, new Separator()); + mMenuManager.insertBefore(endId, new Separator()); + mMenuManager.insertBefore(endId, ExtractIncludeAction.create(mEditor)); + mMenuManager.insertBefore(endId, ExtractStyleAction.create(mEditor)); + mMenuManager.insertBefore(endId, WrapInAction.create(mEditor)); + if (selection.size() == 1 && (selection.get(0).isLayout() || + selection.get(0).getViewInfo().getName().equals(FQCN_GESTURE_OVERLAY_VIEW))) { + mMenuManager.insertBefore(endId, ChangeLayoutAction.create(mEditor)); + } else { + mMenuManager.insertBefore(endId, ChangeViewAction.create(mEditor)); + } + mMenuManager.insertBefore(endId, new Separator()); + } + + /** "Preview List Content" pull-right menu */ + private void insertListPreviewType(String endId) { + + List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); + if (selection.size() == 0) { + return; + } + for (SelectionItem item : selection) { + UiViewElementNode node = item.getViewInfo().getUiViewNode(); + String name = node.getDescriptor().getXmlLocalName(); + if (name.equals(LIST_VIEW) || name.equals(EXPANDABLE_LIST_VIEW)) { + mMenuManager.insertBefore(endId, new Separator()); + mMenuManager.insertBefore(endId, new ListViewTypeMenu(mCanvas)); + return; + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExtractIncludeAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExtractIncludeAction.java deleted file mode 100644 index d3ff090..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExtractIncludeAction.java +++ /dev/null @@ -1,837 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; - -import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_PREFIX; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; -import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; -import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; -import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; -import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; -import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; -import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; -import static com.android.ide.eclipse.adt.AndroidConstants.DOT_XML; -import static com.android.ide.eclipse.adt.AndroidConstants.WS_SEP; -import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS; -import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON; -import static com.android.resources.ResourceType.LAYOUT; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; -import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.ResourceNameValidator; -import com.android.util.Pair; - -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Path; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.dialogs.IInputValidator; -import org.eclipse.jface.dialogs.InputDialog; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.window.Window; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.ide.IDE; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; -import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; -import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Extracts the selection and writes it out as a separate layout file, then adds an - * include to that new layout file. Interactively asks the user for a new name for the - * layout. - */ -@SuppressWarnings("restriction") // For XML model -public class ExtractIncludeAction extends Action { - private LayoutCanvas mCanvas; - - public ExtractIncludeAction(LayoutCanvas canvas) { - super("Extract as Include...", IAction.AS_PUSH_BUTTON); - mCanvas = canvas; - } - - @Override - public boolean isEnabled() { - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 0) { - return false; - } - - // Can't extract the root -- wouldn't that be pointless? (or maybe not always) - for (SelectionItem item : selection) { - if (item.isRoot()) { - return false; - } - } - - // Disable if you've selected a single include tag - if (selection.size() == 1) { - UiViewElementNode uiNode = selection.get(0).getViewInfo().getUiViewNode(); - if (uiNode != null) { - Node xmlNode = uiNode.getXmlNode(); - if (xmlNode.getLocalName().equals(LayoutDescriptors.VIEW_INCLUDE)) { - return false; - } - } - } - - // Enforce that the selection is -contiguous- - if (selection.size() > 1) { - // All elements must be siblings (e.g. same parent) - List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>(selection.size()); - for (SelectionItem item : selection) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - if (node != null) { - nodes.add(node); - } - } - if (nodes.size() == 0) { - return false; - } - - UiElementNode parent = nodes.get(0).getUiParent(); - for (UiViewElementNode node : nodes) { - if (parent != node.getUiParent()) { - return false; - } - } - // Ensure that the siblings are contiguous; no gaps. - // If we've selected all the children of the parent then we don't need to look. - List<UiElementNode> siblings = parent.getUiChildren(); - if (siblings.size() != nodes.size()) { - Set<UiViewElementNode> nodeSet = new HashSet<UiViewElementNode>(nodes); - boolean inRange = false; - int remaining = nodes.size(); - for (UiElementNode node : siblings) { - boolean in = nodeSet.contains(node); - if (in) { - remaining--; - if (remaining == 0) { - break; - } - inRange = true; - } else if (inRange) { - return false; - } - } - } - } - - return true; - } - - private String inputName() { - IProject project = mCanvas.getLayoutEditor().getProject(); - IInputValidator validator = ResourceNameValidator.create(true, project, LAYOUT); - - String defaultName = ""; //$NON-NLS-1$ - Element primaryNode = getPrimaryNode(); - if (primaryNode != null) { - String id = primaryNode.getAttributeNS(ANDROID_URI, ATTR_ID); - if (id.startsWith(ID_PREFIX) || id.startsWith(NEW_ID_PREFIX)) { - // Use everything following the id/, and make it lowercase since that is - // the convention for layouts - defaultName = id.substring(id.indexOf('/') + 1).toLowerCase(); - if (validator.isValid(defaultName) != null) { // Already exists? - defaultName = ""; //$NON-NLS-1$ - } - } - } - - InputDialog d = new InputDialog(AdtPlugin.getDisplay().getActiveShell(), - "Extract As Include", // title - "New Layout Name", defaultName, validator); - - if (d.open() != Window.OK) { - return null; - } - - return d.getValue().trim(); - } - - @Override - public void run() { - String newName = inputName(); - if (newName == null) { - // User canceled - return; - } - - // Create extracted content - // In order to ensure that we preserve as much of the user's original formatting - // and attribute order as possible, we will just snip out the exact element ranges - // from the current source editor and reindent them in the new file - Pair<Integer, Integer> range = computeExtractRange(); - if (range == null) { - return; - } - int start = range.getFirst(); - int end = range.getSecond(); - String extractedText = getExtractedText(start, end); - - Pair<String, String> namespace = computeNamespaces(); - String androidNsPrefix = namespace.getFirst(); - String namespaceDeclarations = namespace.getSecond(); - - // Insert namespace: - extractedText = insertNamespace(extractedText, namespaceDeclarations); - - StringBuilder sb = new StringBuilder(); - sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$ - sb.append(extractedText); - sb.append('\n'); - - String newFileName = newName + DOT_XML; - IProject project = mCanvas.getLayoutEditor().getProject(); - IContainer parent = mCanvas.getLayoutEditor().getInputFile().getParent(); - IPath parentPath = parent.getProjectRelativePath(); - IFile file = project.getFile(new Path(parentPath + WS_SEP + newFileName)); - - writeFile(file, sb.toString()); - - // Force refresh to pick up the newly available @layout/<newName> - LayoutEditor editor = mCanvas.getLayoutEditor(); - editor.getGraphicalEditor().refreshProjectResources(); - - String referenceId = getReferenceId(); - List<Edit> edits = new ArrayList<Edit>(); - - // Replace existing elements in the source file and insert <include> - String include = computeIncludeString(newName, androidNsPrefix, referenceId); - edits.add(new Edit(start, end, include)); - - // Update any layout references to the old id with the new id - if (referenceId != null) { - String rootId = getRootId(); - IStructuredModel model = editor.getModelForRead(); - try { - IStructuredDocument doc = model.getStructuredDocument(); - if (doc != null) { - List<Edit> replaceIds = replaceIds(doc, start, end, rootId, referenceId); - edits.addAll(replaceIds); - } - } finally { - model.releaseFromRead(); - } - } - - // Open extracted file. This seems to trigger refreshing of ProjectResources - // such that the @layout/<newName> reference from the new <include> we're adding - // will work; without this we get file reference errors - openFile(file); - - // Perform edits - applyEdits("Extract As Include", edits); - } - - /** Produce a list of edits to replace references to the given id with the given new id */ - private List<Edit> replaceIds(IStructuredDocument doc, int skipStart, int skipEnd, - String rootId, String referenceId) { - - // We need to search for either @+id/ or @id/ - String match1 = rootId; - String match2; - if (match1.startsWith(ID_PREFIX)) { - match2 = '"' + NEW_ID_PREFIX + match1.substring(ID_PREFIX.length()) + '"'; - match1 = '"' + match1 + '"'; - } else if (match1.startsWith(NEW_ID_PREFIX)) { - match2 = '"' + ID_PREFIX + match1.substring(NEW_ID_PREFIX.length()) + '"'; - match1 = '"' + match1 + '"'; - } else { - return Collections.emptyList(); - } - - String namePrefix = ANDROID_NS_PREFIX + ':' + ATTR_LAYOUT_PREFIX; - List<Edit> edits = new ArrayList<Edit>(); - - IStructuredDocumentRegion region = doc.getFirstStructuredDocumentRegion(); - for (; region != null; region = region.getNext()) { - ITextRegionList list = region.getRegions(); - int regionStart = region.getStart(); - - // Look at all attribute values and look for an id reference match - String attributeName = ""; //$NON-NLS-1$ - for (int j = 0; j < region.getNumberOfRegions(); j++) { - ITextRegion subRegion = list.get(j); - String type = subRegion.getType(); - if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) { - attributeName = region.getText(subRegion); - } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { - // Only replace references in layout attributes - if (!attributeName.startsWith(namePrefix)) { - continue; - } - // Skip occurrences in the given skip range - int subRegionStart = regionStart + subRegion.getStart(); - if (subRegionStart >= skipStart && subRegionStart <= skipEnd) { - continue; - } - - String attributeValue = region.getText(subRegion); - if (attributeValue.equals(match1) || attributeValue.equals(match2)) { - int start = subRegionStart + 1; // skip quote - int end = start + rootId.length(); - edits.add(new Edit(start, end, referenceId)); - } - } - } - } - - return edits; - } - - /** Returns the id to be used for the include tag itself (may be null) */ - private String getReferenceId() { - String rootId = getRootId(); - if (rootId != null) { - return rootId + "_ref"; - } - - return null; - } - - /** Get the id of the root selected element, if any */ - private String getRootId() { - Element primaryNode = getPrimaryNode(); - if (primaryNode != null) { - String oldId = primaryNode.getAttributeNS(ANDROID_URI, ATTR_ID); - if (oldId.length() > 0) { - return oldId; - } - } - - return null; - } - - private boolean writeFile(IFile file, String content) { - // Write out the content into the new XML file - try { - byte[] buf = content.getBytes("UTF8"); //$NON-NLS-1$ - InputStream stream = new ByteArrayInputStream(buf); - file.create(stream, true /* force */, null /* progress */); - return true; - } catch (Exception e) { - String message = e.getMessage(); - String error = String.format("Failed to generate %1$s: %2$s", file.getName(), message); - AdtPlugin.displayError("Extract As Include", error); - return false; - } - } - - private Pair<String, String> computeNamespaces() { - String androidNsPrefix = null; - String namespaceDeclarations = null; - - Document document = getDocument(); - if (document != null) { - StringBuilder sb = new StringBuilder(); - Element root = document.getDocumentElement(); - NamedNodeMap attributes = root.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attributeNode = attributes.item(i); - - String prefix = attributeNode.getPrefix(); - if (XMLNS.equals(prefix)) { - sb.append(' '); - String name = attributeNode.getNodeName(); - sb.append(name); - sb.append('=').append('"'); - - String value = attributeNode.getNodeValue(); - if (value.equals(ANDROID_URI)) { - androidNsPrefix = name; - if (androidNsPrefix.startsWith(XMLNS_COLON)) { - androidNsPrefix = androidNsPrefix.substring(XMLNS_COLON.length()); - } - } - sb.append(DescriptorsUtils.toXmlAttributeValue(value)); - sb.append('"'); - } - } - - namespaceDeclarations = sb.toString(); - } - - if (androidNsPrefix == null) { - androidNsPrefix = ANDROID_NS_PREFIX; - } - if (namespaceDeclarations == null) { - StringBuilder sb = new StringBuilder(); - sb.append(' '); - sb.append(XMLNS_COLON); - sb.append(ANDROID_NS_PREFIX); - sb.append('=').append('"'); - sb.append(ANDROID_URI); - sb.append('"'); - namespaceDeclarations = sb.toString(); - } - - return Pair.of(androidNsPrefix, namespaceDeclarations); - } - - private String insertNamespace(String xmlText, String namespaceDeclarations) { - // Insert namespace declarations into the extracted XML fragment - int firstSpace = xmlText.indexOf(' '); - int elementEnd = xmlText.indexOf('>'); - int insertAt; - if (firstSpace != -1 && firstSpace < elementEnd) { - insertAt = firstSpace; - } else { - insertAt = elementEnd; - } - xmlText = xmlText.substring(0, insertAt) + namespaceDeclarations - + xmlText.substring(insertAt); - - return xmlText; - } - - private void openFile(IFile file) { - LayoutEditor editor = mCanvas.getLayoutEditor(); - GraphicalEditorPart graphicalEditor = editor.getGraphicalEditor(); - IFile leavingFile = graphicalEditor.getEditedFile(); - - try { - // Duplicate the current state into the newly created file - QualifiedName qname = ConfigurationComposite.NAME_CONFIG_STATE; - String state = AdtPlugin.getFileProperty(leavingFile, qname); - file.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE, state); - } catch (CoreException e) { - // pass - } - - /* TBD: "Show Included In" if supported. - * Not sure if this is a good idea. - if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { - try { - Reference include = Reference.create(graphicalEditor.getEditedFile()); - file.setSessionProperty(GraphicalEditorPart.NAME_INCLUDE, include); - } catch (CoreException e) { - // pass - worst that can happen is that we don't start with inclusion - } - } - */ - - try { - IEditorPart part = IDE.openEditor(editor.getEditorSite().getPage(), file); - if (part instanceof AndroidXmlEditor && AdtPrefs.getPrefs().getFormatXml()) { - AndroidXmlEditor newEditor = (AndroidXmlEditor) part; - newEditor.reformatDocument(); - } - } catch (PartInitException e) { - AdtPlugin.log(e, "Can't open new included layout"); - } - } - - /** - * Compute the actual {@code <include>} string to be inserted in place of the old - * selection - */ - private String computeIncludeString(String newName, String androidNsPrefix, - String referenceId) { - StringBuilder sb = new StringBuilder(); - sb.append("<include layout=\"@layout/"); //$NON-NLS-1$ - sb.append(newName); - sb.append('"'); - sb.append(' '); - - // Create new id for the include itself - if (referenceId != null) { - sb.append(androidNsPrefix); - sb.append(':'); - sb.append(ATTR_ID); - sb.append('=').append('"'); - sb.append(referenceId); - sb.append('"').append(' '); - } - - // Add id string, unless it's a <merge>, since we may need to adjust any layout - // references to apply to the <include> tag instead - // TODO: Use refactoring infrastructure to handle this part - - // I should move all the layout_ attributes as well - // I also need to duplicate and modify the id and then replace - // everything else in the file with this new id... - - // HACK: see issue 13494: We must duplicate the width/height attributes on the - // <include> statement for designtime rendering only - Element primaryNode = getPrimaryNode(); - String width = null; - String height = null; - if (primaryNode == null) { - // Multiple selection - in that case we will be creating an outer <merge> - // so we need to set our own width/height on it - width = height = VALUE_WRAP_CONTENT; - } else { - if (!primaryNode.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH)) { - width = VALUE_WRAP_CONTENT; - } else { - width = primaryNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH); - } - if (!primaryNode.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT)) { - height = VALUE_WRAP_CONTENT; - } else { - height = primaryNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT); - } - } - if (width != null) { - sb.append(' '); - sb.append(androidNsPrefix); - sb.append(':'); - sb.append(ATTR_LAYOUT_WIDTH); - sb.append('=').append('"'); - sb.append(DescriptorsUtils.toXmlAttributeValue(width)); - sb.append('"'); - } - if (height != null) { - sb.append(' '); - sb.append(androidNsPrefix); - sb.append(':'); - sb.append(ATTR_LAYOUT_HEIGHT); - sb.append('=').append('"'); - sb.append(DescriptorsUtils.toXmlAttributeValue(height)); - sb.append('"'); - } - - // Duplicate all the other layout attributes as well - if (primaryNode != null) { - NamedNodeMap attributes = primaryNode.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attr = attributes.item(i); - String name = attr.getLocalName(); - if (name.startsWith(ATTR_LAYOUT_PREFIX) - && ANDROID_URI.equals(attr.getNamespaceURI())) { - if (name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) { - // Already handled - continue; - } - - sb.append(' '); - sb.append(androidNsPrefix); - sb.append(':'); - sb.append(name); - sb.append('=').append('"'); - sb.append(DescriptorsUtils.toXmlAttributeValue(attr.getNodeValue())); - sb.append('"'); - } - } - } - - sb.append("/>"); - return sb.toString(); - } - - /** Return the text in the document in the range start to end */ - private String getExtractedText(int start, int end) { - LayoutEditor editor = mCanvas.getLayoutEditor(); - - IStructuredModel model = editor.getModelForRead(); - try { - IStructuredDocument document = editor.getStructuredDocument(); - String xml = document.get(start, end - start); - Element primaryNode = getPrimaryNode(); - xml = stripTopLayoutAttributes(primaryNode, start, xml); - xml = dedent(xml); - - // Wrap siblings in <merge>? - if (primaryNode == null) { - StringBuilder sb = new StringBuilder(); - sb.append("<merge>\n"); //$NON-NLS-1$ - // indent an extra level - for (String line : xml.split("\n")) { //$NON-NLS-1$ - sb.append(" "); //$NON-NLS-1$ - sb.append(line).append('\n'); - } - sb.append("</merge>\n"); //$NON-NLS-1$ - xml = sb.toString(); - } - - return xml; - } catch (BadLocationException e) { - // the region offset was invalid. ignore. - return null; - } finally { - model.releaseFromRead(); - } - } - - /** Remove sections of the document that correspond to top level layout attributes; - * these are placed on the include element instead */ - private String stripTopLayoutAttributes(Element primaryNode, int start, String xml) { - if (primaryNode != null) { - // List of attributes to remove - //IndexedRegion attRegion = (IndexedRegion) attrs.item(i); - List<IndexedRegion> skip = new ArrayList<IndexedRegion>(); - NamedNodeMap attributes = primaryNode.getAttributes(); - for (int i = 0, n = attributes.getLength(); i < n; i++) { - Node attr = attributes.item(i); - String name = attr.getLocalName(); - if (name.startsWith(ATTR_LAYOUT_PREFIX) - && ANDROID_URI.equals(attr.getNamespaceURI())) { - if (name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) { - // These are special and are left in - continue; - } - - if (attr instanceof IndexedRegion) { - skip.add((IndexedRegion) attr); - } - } - } - if (skip.size() > 0) { - Collections.sort(skip, new Comparator<IndexedRegion>() { - // Sort in start order - public int compare(IndexedRegion r1, IndexedRegion r2) { - return r1.getStartOffset() - r2.getStartOffset(); - } - }); - - // Successively cut out the various layout attributes - // TODO remove adjacent whitespace too (but not newlines, unless they - // are newly adjacent) - StringBuilder sb = new StringBuilder(xml.length()); - int nextStart = 0; - - // Copy out all the sections except the skip sections - for (IndexedRegion r : skip) { - int regionStart = r.getStartOffset(); - // Adjust to string offsets since we've copied the string out of - // the document - regionStart -= start; - - sb.append(xml.substring(nextStart, regionStart)); - - nextStart = regionStart + r.getLength(); - } - if (nextStart < xml.length()) { - sb.append(xml.substring(nextStart)); - } - - return sb.toString(); - } - } - - return xml; - } - - private static String getIndent(String line, int max) { - int i = 0; - int n = Math.min(max, line.length()); - for (; i < n; i++) { - char c = line.charAt(i); - if (!Character.isWhitespace(c)) { - return line.substring(0, i); - } - } - - if (n < line.length()) { - return line.substring(0, n); - } else { - return line; - } - } - - private static String dedent(String xml) { - String[] lines = xml.split("\n"); //$NON-NLS-1$ - if (lines.length < 2) { - // The first line never has any indentation since we copy it out from the - // element start index - return xml; - } - - String indentPrefix = getIndent(lines[1], lines[1].length()); - for (int i = 2, n = lines.length; i < n; i++) { - String line = lines[i]; - - // Ignore blank lines - if (line.trim().length() == 0) { - continue; - } - - indentPrefix = getIndent(line, indentPrefix.length()); - - if (indentPrefix.length() == 0) { - return xml; - } - } - - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - if (line.startsWith(indentPrefix)) { - sb.append(line.substring(indentPrefix.length())); - } else { - sb.append(line); - } - sb.append('\n'); - } - return sb.toString(); - } - - private Element getPrimaryNode() { - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 1) { - UiViewElementNode node = selection.get(0).getViewInfo().getUiViewNode(); - if (node != null) { - Node xmlNode = node.getXmlNode(); - if (xmlNode instanceof Element) { - return (Element) xmlNode; - } - } - } - - return null; - } - - private Document getDocument() { - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - for (SelectionItem item : selection) { - UiViewElementNode node = item.getViewInfo().getUiViewNode(); - if (node != null) { - Node xmlNode = node.getXmlNode(); - if (xmlNode != null) { - return xmlNode.getOwnerDocument(); - } - } - } - - return null; - } - - private Pair<Integer, Integer> computeExtractRange() { - List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); - if (selection.size() == 0) { - return null; - } - int end = Integer.MIN_VALUE; - int start = Integer.MAX_VALUE; - for (SelectionItem item : selection) { - CanvasViewInfo viewInfo = item.getViewInfo(); - UiViewElementNode uiNode = viewInfo.getUiViewNode(); - if (uiNode == null) { - continue; - } - Node xmlNode = uiNode.getXmlNode(); - if (xmlNode instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) xmlNode; - - start = Math.min(start, region.getStartOffset()); - end = Math.max(end, region.getEndOffset()); - } - } - if (start < 0) { - return null; - } - - return Pair.of(start, end); - } - - /** Apply edits into document under an undo lock */ - private void applyEdits(String label, final List<Edit> edits) { - // Process the edits in reverse document position order to ensure - // that the offsets aren't affected by other edits - Collections.sort(edits); - final LayoutEditor editor = mCanvas.getLayoutEditor(); - editor.wrapUndoEditXmlModel(label, new Runnable() { - public void run() { - IStructuredDocument document = editor.getStructuredDocument(); - if (document != null) { - try { - for (Edit edit : edits) { - edit.apply(document); - } - } catch (BadLocationException e) { - AdtPlugin.log(e, "Cannot insert <include> tag"); - return; - } - } - } - }); - - // Save file to trigger include finder scanning (as well as making the - // actual show-include feature work since it relies on reading files from - // disk, not a live buffer) - IEditorPart editorPart = editor; - IWorkbenchPage page = editorPart.getEditorSite().getPage(); - page.saveEditor(editorPart, false); - } - - /** - * Edit operation (insert, delete, replace) at a given offset - a collection of these - * can be sorted in reverse offset order such that they can be applied successively - * without having to updated offsets - * <p> - * TODO: When rewriting this extract operation to the refactoring framework, use - * refactoring framework's change list instead - */ - private static class Edit implements Comparable<Edit> { - private final int mStart; - private final int mEnd; - private final String mReplaceWith; - - public Edit(int start, int end, String replaceWith) { - super(); - mStart = start; - mEnd = end; - mReplaceWith = replaceWith; - } - - void apply(IStructuredDocument document) throws BadLocationException { - document.replace(mStart, mEnd - mStart, mReplaceWith); - } - - public int compareTo(Edit o) { - // Sort in *reverse* offset order - return o.mStart - mStart; - } - - @Override - public String toString() { - return "Edit [start=" + mStart + ", end=" + mEnd + ", replaceWith=" + mReplaceWith - + "]"; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 5506319..337ad5c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -17,9 +17,10 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.ANDROID_STRING_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX; import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW; import static com.android.ide.common.layout.LayoutConstants.STRING_PREFIX; -import static com.android.ide.eclipse.adt.AndroidConstants.ANDROID_PKG; +import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.StaticRenderSession; @@ -32,10 +33,14 @@ import com.android.ide.common.rendering.api.ResourceValue; 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.ResourceFile; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; import com.android.ide.common.sdk.LoadStatus; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.ContextPullParser; @@ -51,27 +56,24 @@ import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutC import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.io.IFileWrapper; import com.android.ide.eclipse.adt.io.IFolderWrapper; +import com.android.io.IAbstractFile; +import com.android.io.StreamException; import com.android.resources.Density; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.StreamException; import com.android.sdklib.xml.AndroidManifest; import org.eclipse.core.resources.IFile; @@ -236,6 +238,7 @@ public class GraphicalEditorPart extends EditorPart private TargetListener mTargetListener; private ConfigListener mConfigListener; + private ResourceResolver mResourceResolver; private ReloadListener mReloadListener; @@ -245,6 +248,14 @@ public class GraphicalEditorPart extends EditorPart private int mTargetSdkVersion; private LayoutActionBar mActionBar; + /** + * Flags which tracks whether this editor is currently active which is set whenever + * {@link #activated()} is called and clear whenever {@link #deactivated()} is called. + * This is used to suppress repeated calls to {@link #activate()} to avoid doing + * unnecessary work. + */ + private boolean mActive; + public GraphicalEditorPart(LayoutEditor layoutEditor) { mLayoutEditor = layoutEditor; setPartName("Graphical Layout"); @@ -322,9 +333,9 @@ public class GraphicalEditorPart extends EditorPart mSashPalette = new SashForm(parent, SWT.HORIZONTAL); mSashPalette.setLayoutData(new GridData(GridData.FILL_BOTH)); - DecorComposite paleteDecor = new DecorComposite(mSashPalette, SWT.BORDER); - paleteDecor.setContent(new PaletteControl.PaletteDecor(this)); - mPalette = (PaletteControl) paleteDecor.getContentControl(); + DecorComposite paletteDecor = new DecorComposite(mSashPalette, SWT.BORDER); + paletteDecor.setContent(new PaletteControl.PaletteDecor(this)); + mPalette = (PaletteControl) paletteDecor.getContentControl(); Composite layoutBarAndCanvas = new Composite(mSashPalette, SWT.NONE); GridLayout gridLayout = new GridLayout(1, false); @@ -428,6 +439,7 @@ public class GraphicalEditorPart extends EditorPart */ public void onConfigurationChange() { mConfiguredFrameworkRes = mConfiguredProjectRes = null; + mResourceResolver = null; if (mEditedFile == null || mConfigComposite.getEditedConfig() == null) { return; @@ -503,6 +515,7 @@ public class GraphicalEditorPart extends EditorPart public void onThemeChange() { // Store the state in the current file mConfigComposite.storeState(); + mResourceResolver = null; recomputeLayout(); @@ -533,7 +546,7 @@ public class GraphicalEditorPart extends EditorPart public Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources() { if (mConfiguredFrameworkRes == null && mConfigComposite != null) { - ProjectResources frameworkRes = getFrameworkResources(); + ResourceRepository frameworkRes = getFrameworkResources(); if (frameworkRes == null) { AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework"); @@ -551,9 +564,6 @@ public class GraphicalEditorPart extends EditorPart if (mConfiguredProjectRes == null && mConfigComposite != null) { ProjectResources project = getProjectResources(); - // make sure they are loaded - project.loadAll(); - // get the project resource values based on the current config mConfiguredProjectRes = project.getConfiguredResources( mConfigComposite.getCurrentConfig()); @@ -567,7 +577,7 @@ public class GraphicalEditorPart extends EditorPart * configuration selection. * @return the framework resources or null if not found. */ - public ProjectResources getFrameworkResources() { + public ResourceRepository getFrameworkResources() { return getFrameworkResources(getRenderingTarget()); } @@ -577,7 +587,7 @@ public class GraphicalEditorPart extends EditorPart * @param target the target for which to return the framework resources. * @return the framework resources or null if not found. */ - public ProjectResources getFrameworkResources(IAndroidTarget target) { + public ResourceRepository getFrameworkResources(IAndroidTarget target) { if (target != null) { AndroidTargetData data = Sdk.getCurrent().getTargetData(target); @@ -589,7 +599,6 @@ public class GraphicalEditorPart extends EditorPart return null; } - public ProjectResources getProjectResources() { if (mEditedFile != null) { ResourceManager manager = ResourceManager.getInstance(); @@ -716,6 +725,21 @@ public class GraphicalEditorPart extends EditorPart } }.schedule(); } + + /** + * When the device changes, zoom the view to fit, but only up to 100% (e.g. zoom + * out to fit the content, or zoom back in if we were zoomed out more from the + * previous view, but only up to 100% such that we never blow up pixels + */ + public void onDevicePostChange() { + if (mActionBar.isZoomingAllowed()) { + getCanvasControl().setFitScale(true); + } + } + + public String getIncludedWithin() { + return mIncludedWithin != null ? mIncludedWithin.getName() : null; + } } /** @@ -759,6 +783,7 @@ public class GraphicalEditorPart extends EditorPart // because the target changed we must reset the configured resources. mConfiguredFrameworkRes = mConfiguredProjectRes = null; + mResourceResolver = null; // make sure we remove the custom view loader, since its parent class loader is the // bridge class loader. @@ -775,9 +800,9 @@ public class GraphicalEditorPart extends EditorPart } /** Refresh the configured project resources associated with this editor */ - /*package*/ void refreshProjectResources() { + public void refreshProjectResources() { mConfiguredProjectRes = null; - mConfigListener.getConfiguredProjectResources(); + mResourceResolver = null; } /** @@ -866,8 +891,18 @@ public class GraphicalEditorPart extends EditorPart * Responds to a page change that made the Graphical editor page the activated page. */ public void activated() { - if (mNeedsRecompute) { - recomputeLayout(); + if (!mActive) { + mActive = true; + + boolean changed = mConfigComposite.syncRenderState(); + if (changed) { + // Will also force recomputeLayout() + return; + } + + if (mNeedsRecompute) { + recomputeLayout(); + } } } @@ -875,7 +910,7 @@ public class GraphicalEditorPart extends EditorPart * Responds to a page change that made the Graphical editor page the deactivated page */ public void deactivated() { - // nothing to be done here for now. + mActive = false; } /** @@ -1027,7 +1062,7 @@ public class GraphicalEditorPart extends EditorPart new StaticRenderSession( Result.Status.SUCCESS.createResult(), null /*rootViewInfo*/, null /*image*/), - null /*explodeNodes*/); + null /*explodeNodes*/, true /* layoutlib5 */); return; } @@ -1313,7 +1348,8 @@ public class GraphicalEditorPart extends EditorPart explodeNodes, null /*custom background*/, false /*no decorations*/, logger, mIncludedWithin, renderingMode); - canvas.setSession(session, explodeNodes); + boolean layoutlib5 = layoutLib.supports(Capability.EMBEDDED_LAYOUT); + canvas.setSession(session, explodeNodes, layoutlib5); // update the UiElementNode with the layout info. if (session != null && session.getResult().isSuccess() == false) { @@ -1351,60 +1387,33 @@ public class GraphicalEditorPart extends EditorPart model.refreshUi(); } - private RenderSession renderWithBridge(IProject iProject, UiDocumentNode model, + private RenderSession renderWithBridge(IProject project, UiDocumentNode model, LayoutLibrary layoutLib, int width, int height, Set<UiElementNode> explodeNodes, Integer overrideBgColor, boolean noDecor, LayoutLog logger, Reference includeWithin, RenderingMode renderingMode) { ResourceManager resManager = ResourceManager.getInstance(); - ProjectResources projectRes = resManager.getProjectResources(iProject); + ProjectResources projectRes = resManager.getProjectResources(project); if (projectRes == null) { displayError("Missing project resources."); return null; } - // Get the resources of the file's project. - Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = - mConfigListener.getConfiguredProjectResources(); - - // Get the framework resources - Map<ResourceType, Map<String, ResourceValue>> frameworkResources = - mConfigListener.getConfiguredFrameworkResources(); - - // Abort the rendering if the resources are not found. - if (configuredProjectRes == null) { - displayError("Missing project resources for current configuration."); - return null; - } - - if (frameworkResources == null) { - displayError("Missing framework resources."); - return null; - } - // Lazily create the project callback the first time we need it if (mProjectCallback == null) { - mProjectCallback = new ProjectCallback( - layoutLib.getClassLoader(), projectRes, iProject); + mProjectCallback = new ProjectCallback(layoutLib, projectRes, project); } else { // Also clears the set of missing/broken classes prior to rendering mProjectCallback.getMissingClasses().clear(); mProjectCallback.getUninstantiatableClasses().clear(); } - // get the selected theme - String theme = mConfigComposite.getTheme(); - if (theme == null) { - displayError("Missing theme."); - return null; - } - if (mUseExplodeMode) { // compute how many padding in x and y will bump the screen size List<UiElementNode> children = model.getUiChildren(); if (children.size() == 1) { ExplodedRenderingHelper helper = new ExplodedRenderingHelper( - children.get(0).getXmlNode(), iProject); + children.get(0).getXmlNode(), project); // there are 2 paddings for each view // left and right, or top and bottom. @@ -1418,22 +1427,22 @@ public class GraphicalEditorPart extends EditorPart Density density = mConfigComposite.getDensity(); float xdpi = mConfigComposite.getXDpi(); float ydpi = mConfigComposite.getYDpi(); - boolean isProjectTheme = mConfigComposite.isProjectTheme(); ILayoutPullParser modelParser = new UiElementPullParser(model, - mUseExplodeMode, explodeNodes, density, xdpi, iProject); + mUseExplodeMode, explodeNodes, density, xdpi, project); ILayoutPullParser topParser = modelParser; // Code to support editing included layout + // first reset the layout parser just in case. + mProjectCallback.setLayoutParser(null, null); - // Outer layout name: if (includeWithin != null) { + // Outer layout name: String contextLayoutName = includeWithin.getName(); // Find the layout file. - Map<String, ResourceValue> layouts = configuredProjectRes.get( - ResourceType.LAYOUT); - ResourceValue contextLayout = layouts.get(contextLayoutName); + ResourceValue contextLayout = getResourceResolver().findResValue( + LAYOUT_PREFIX + contextLayoutName , false /* forceFrameworkOnly*/); if (contextLayout != null) { File layoutFile = new File(contextLayout.getValue()); if (layoutFile.isFile()) { @@ -1441,7 +1450,8 @@ public class GraphicalEditorPart extends EditorPart // Get the name of the layout actually being edited, without the extension // as it's what IXmlPullParser.getParser(String) will receive. String queryLayoutName = getLayoutResourceName(); - topParser = new ContextPullParser(queryLayoutName, modelParser); + mProjectCallback.setLayoutParser(queryLayoutName, modelParser); + topParser = new ContextPullParser(mProjectCallback); topParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); topParser.setInput(new FileReader(layoutFile)); } catch (XmlPullParserException e) { @@ -1453,15 +1463,16 @@ public class GraphicalEditorPart extends EditorPart } } - // FIXME: make resource resolver persistent, and only update it when something changes. - ResourceResolver resolver = ResourceResolver.create( - configuredProjectRes, frameworkResources, - theme, isProjectTheme); + ResourceResolver resolver = getResourceResolver(); + if (resolver == null) { + // Abort the rendering if the resources are not found. + return null; + } SessionParams params = new SessionParams( topParser, renderingMode, - iProject /* projectKey */, + project /* projectKey */, width, height, density, xdpi, ydpi, resolver, @@ -1471,19 +1482,11 @@ public class GraphicalEditorPart extends EditorPart logger); if (noDecor) { params.setForceNoDecor(); - } - - // FIXME make persistent and only reload when the manifest (or at least resources) chanage. - IFolderWrapper projectFolder = new IFolderWrapper(getProject()); - IAbstractFile manifest = AndroidManifest.getManifest(projectFolder); - if (manifest != null) { - try { - params.setAppIcon(AndroidManifest.getApplicationIcon(manifest)); - } catch (Exception e) { - // ignore. - } + } else { + ManifestInfo manifestInfo = ManifestInfo.get(project); try { - params.setAppLabel(AndroidManifest.getApplicationLabel(manifest)); + params.setAppLabel(manifestInfo.getApplicationLabel()); + params.setAppIcon(manifestInfo.getApplicationIcon()); } catch (Exception e) { // ignore. } @@ -1523,7 +1526,7 @@ public class GraphicalEditorPart extends EditorPart * @return the image, or null if something went wrong */ BufferedImage renderThemeItem(String itemName, int width, int height) { - ResourceResolver resources = createResolver(); + ResourceResolver resources = getResourceResolver(); LayoutLibrary layoutLibrary = getLayoutLibrary(); IProject project = getProject(); ResourceValue drawableResourceValue = resources.findItemInTheme(itemName); @@ -1532,8 +1535,7 @@ public class GraphicalEditorPart extends EditorPart float ydpi = mConfigComposite.getYDpi(); ResourceManager resManager = ResourceManager.getInstance(); ProjectResources projectRes = resManager.getProjectResources(project); - ProjectCallback projectCallback = new ProjectCallback( - layoutLibrary.getClassLoader(), projectRes, project); + ProjectCallback projectCallback = new ProjectCallback(layoutLibrary, projectRes, project); LayoutLog silentLogger = new LayoutLog(); DrawableParams params = new DrawableParams(drawableResourceValue, project, width, height, @@ -1551,19 +1553,44 @@ public class GraphicalEditorPart extends EditorPart return null; } - ResourceResolver createResolver() { - String theme = mConfigComposite.getTheme(); - boolean isProjectTheme = mConfigComposite.isProjectTheme(); - Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = - mConfigListener.getConfiguredProjectResources(); + /** + * Returns the {@link ResourceResolver} for this editor + * + * @return the resolver used to resolve resources for the current configuration of + * this editor, or null + */ + public ResourceResolver getResourceResolver() { + if (mResourceResolver == null) { + String theme = mConfigComposite.getTheme(); + if (theme == null) { + displayError("Missing theme."); + return null; + } + boolean isProjectTheme = mConfigComposite.isProjectTheme(); + + Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = + mConfigListener.getConfiguredProjectResources(); - // Get the framework resources - Map<ResourceType, Map<String, ResourceValue>> frameworkResources = - mConfigListener.getConfiguredFrameworkResources(); + // Get the framework resources + Map<ResourceType, Map<String, ResourceValue>> frameworkResources = + mConfigListener.getConfiguredFrameworkResources(); + + if (configuredProjectRes == null) { + displayError("Missing project resources for current configuration."); + return null; + } + + if (frameworkResources == null) { + displayError("Missing framework resources."); + return null; + } + + mResourceResolver = ResourceResolver.create( + configuredProjectRes, frameworkResources, + theme, isProjectTheme); + } - return ResourceResolver.create( - configuredProjectRes, frameworkResources, - theme, isProjectTheme); + return mResourceResolver; } /** @@ -1630,7 +1657,8 @@ public class GraphicalEditorPart extends EditorPart assert mConfigComposite.getDisplay().getThread() == Thread.currentThread(); boolean recompute = false; - if (flags.rClass) { + // we only care about the r class of the main project. + if (flags.rClass && libraryChanged == false) { recompute = true; if (mEditedFile != null) { ResourceManager manager = ResourceManager.getInstance(); @@ -1653,8 +1681,7 @@ public class GraphicalEditorPart extends EditorPart } // if a resources was modified. - // also, if a layout in a library was modified. - if (flags.resources || (libraryChanged && flags.layout)) { + if (flags.resources) { recompute = true; // TODO: differentiate between single and multi resource file changed, and whether @@ -1662,6 +1689,7 @@ public class GraphicalEditorPart extends EditorPart // force a reparse in case a value XML file changed. mConfiguredProjectRes = null; + mResourceResolver = null; // clear the cache in the bridge in case a bitmap/9-patch changed. LayoutLibrary layoutLib = getReadyLayoutLib(true /*displayError*/); @@ -1792,7 +1820,7 @@ public class GraphicalEditorPart extends EditorPart if (severity == IMarker.SEVERITY_ERROR) { hasJavaErrors = true; } - } else if (markerType.equals(AndroidConstants.MARKER_AAPT_COMPILE)) { + } else if (markerType.equals(AdtConstants.MARKER_AAPT_COMPILE)) { int severity = marker.getAttribute(IMarker.SEVERITY, -1); if (severity == IMarker.SEVERITY_ERROR) { hasAaptErrors = true; @@ -2027,7 +2055,7 @@ public class GraphicalEditorPart extends EditorPart if (r instanceof ClassLinkStyleRange) { String fqcn = mErrorLabel.getText(r.start, r.start + r.length - 1); - if (!Hyperlinks.openJavaClass(getProject(), fqcn)) { + if (!AdtPlugin.openJavaClass(getProject(), fqcn)) { createNewClass(fqcn); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java index 27a6024..2f46921 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/HoverOverlay.java @@ -16,22 +16,35 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtDrawingStyle.HOVER; +import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtDrawingStyle.HOVER_SELECTION; + import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; +import java.util.List; + /** * The {@link HoverOverlay} paints an optional hover on top of the layout, * highlighting the currently hovered view. */ public class HoverOverlay extends Overlay { + private final LayoutCanvas mCanvas; + /** Hover border color. Must be disposed, it's NOT a system color. */ private Color mHoverStrokeColor; /** Hover fill color. Must be disposed, it's NOT a system color. */ private Color mHoverFillColor; + /** Hover border select color. Must be disposed, it's NOT a system color. */ + private Color mHoverSelectStrokeColor; + + /** Hover fill select color. Must be disposed, it's NOT a system color. */ + private Color mHoverSelectFillColor; + /** Vertical scaling & scrollbar information. */ private CanvasTransform mVScale; @@ -48,13 +61,14 @@ public class HoverOverlay extends Overlay { /** * Constructs a new {@link HoverOverlay} linked to the given view hierarchy. * + * @param canvas the associated canvas * @param hScale The {@link CanvasTransform} to use to transfer horizontal layout * coordinates to screen coordinates. * @param vScale The {@link CanvasTransform} to use to transfer vertical layout * coordinates to screen coordinates. */ - public HoverOverlay(CanvasTransform hScale, CanvasTransform vScale) { - super(); + public HoverOverlay(LayoutCanvas canvas, CanvasTransform hScale, CanvasTransform vScale) { + mCanvas = canvas; this.mHScale = hScale; this.mVScale = vScale; } @@ -67,6 +81,15 @@ public class HoverOverlay extends Overlay { if (SwtDrawingStyle.HOVER.getFillColor() != null) { mHoverFillColor = new Color(device, SwtDrawingStyle.HOVER.getFillColor()); } + + if (SwtDrawingStyle.HOVER_SELECTION.getStrokeColor() != null) { + mHoverSelectStrokeColor = new Color(device, + SwtDrawingStyle.HOVER_SELECTION.getStrokeColor()); + } + if (SwtDrawingStyle.HOVER_SELECTION.getFillColor() != null) { + mHoverSelectFillColor = new Color(device, + SwtDrawingStyle.HOVER_SELECTION.getFillColor()); + } } @Override @@ -80,6 +103,16 @@ public class HoverOverlay extends Overlay { mHoverFillColor.dispose(); mHoverFillColor = null; } + + if (mHoverSelectStrokeColor != null) { + mHoverSelectStrokeColor.dispose(); + mHoverSelectStrokeColor = null; + } + + if (mHoverSelectFillColor != null) { + mHoverSelectFillColor.dispose(); + mHoverSelectFillColor = null; + } } /** @@ -117,19 +150,35 @@ public class HoverOverlay extends Overlay { int w = mHScale.scale(mHoverRect.width); int h = mVScale.scale(mHoverRect.height); - if (mHoverStrokeColor != null) { + + boolean hoverIsSelected = false; + List<SelectionItem> selections = mCanvas.getSelectionManager().getSelections(); + for (SelectionItem item : selections) { + if (mHoverRect.equals(item.getViewInfo().getSelectionRect())) { + hoverIsSelected = true; + break; + } + } + + Color stroke = hoverIsSelected ? mHoverSelectStrokeColor : mHoverStrokeColor; + Color fill = hoverIsSelected ? mHoverSelectFillColor : mHoverFillColor; + + if (stroke != null) { int oldAlpha = gc.getAlpha(); - gc.setForeground(mHoverStrokeColor); - gc.setLineStyle(SwtDrawingStyle.HOVER.getLineStyle()); - gc.setAlpha(SwtDrawingStyle.HOVER.getStrokeAlpha()); + gc.setForeground(stroke); + gc.setLineStyle(hoverIsSelected ? + HOVER_SELECTION.getLineStyle() : HOVER.getLineStyle()); + gc.setAlpha(hoverIsSelected ? + HOVER_SELECTION.getStrokeAlpha() : HOVER.getStrokeAlpha()); gc.drawRectangle(x, y, w, h); gc.setAlpha(oldAlpha); } - if (mHoverFillColor != null) { + if (fill != null) { int oldAlpha = gc.getAlpha(); - gc.setAlpha(SwtDrawingStyle.HOVER.getFillAlpha()); - gc.setBackground(mHoverFillColor); + gc.setAlpha(hoverIsSelected ? + HOVER_SELECTION.getFillAlpha() : HOVER.getFillAlpha()); + gc.setBackground(fill); gc.fillRectangle(x, y, w, h); gc.setAlpha(oldAlpha); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java index d3349e4..7af89f8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java @@ -49,14 +49,16 @@ public class ImageControl extends Canvas implements MouseTrackListener { private float mScale = 1.0f; /** - * Creates an ImageControl rendering the given image, which will be dispose when this - * control is disposed + * Creates an ImageControl rendering the given image, which will be disposed when this + * control is disposed (unless the {@link #setDisposeImage} method is called to turn + * off auto dispose). * * @param parent the parent to add the image control to * @param style the SWT style to use * @param image the image to be rendered, which must not be null and should be unique * for this image control since it will be disposed by this control when - * the control is disposed + * the control is disposed (unless the {@link #setDisposeImage} method is + * called to turn off auto dispose) */ public ImageControl(Composite parent, int style, Image image) { super(parent, style | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED); @@ -147,14 +149,21 @@ public class ImageControl extends Canvas implements MouseTrackListener { int destWidth = imageWidth; int destHeight = imageHeight; + int oldGcAlias = gc.getAntialias(); + int oldGcInterpolation = gc.getInterpolation(); if (mScale != 1.0f) { destWidth = (int) (mScale * destWidth); destHeight = (int) (mScale * destHeight); + gc.setAntialias(SWT.ON); + gc.setInterpolation(SWT.HIGH); } gc.drawImage(mImage, 0, 0, imageWidth, imageHeight, rect.x + mLeftMargin, rect.y + mTopMargin, destWidth, destHeight); + gc.setAntialias(oldGcAlias); + gc.setInterpolation(oldGcInterpolation); + if (mHoverColor != null && mMouseIn) { gc.setAlpha(60); gc.setBackground(mHoverColor); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java index fedca9c..7832197 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java @@ -16,29 +16,30 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; -import static com.android.ide.eclipse.adt.AndroidConstants.EXT_XML; -import static com.android.ide.eclipse.adt.AndroidConstants.WS_LAYOUTS; -import static com.android.ide.eclipse.adt.AndroidConstants.WS_SEP; -import static com.android.sdklib.SdkConstants.FD_LAYOUT; +import static com.android.AndroidConstants.FD_RES_LAYOUT; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; +import static com.android.resources.ResourceType.LAYOUT; import static org.eclipse.core.resources.IResourceDelta.ADDED; import static org.eclipse.core.resources.IResourceDelta.CHANGED; import static org.eclipse.core.resources.IResourceDelta.CONTENT; import static org.eclipse.core.resources.IResourceDelta.REMOVED; import com.android.annotations.VisibleForTesting; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; +import com.android.ide.common.resources.ResourceItem; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResourceItem; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager.IResourceListener; import com.android.ide.eclipse.adt.io.IFileWrapper; +import com.android.io.IAbstractFile; import com.android.resources.ResourceType; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.IAbstractFile; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; @@ -61,9 +62,11 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -87,7 +90,7 @@ public class IncludeFinder { * {@link IncludeFinder} for this project */ private final static QualifiedName INCLUDE_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID, - "finder"); //$NON-NLS-1$ + "includefinder"); //$NON-NLS-1$ /** Project that the include finder locates includes for */ private final IProject mProject; @@ -183,7 +186,27 @@ public class IncludeFinder { } } - /** For test suite only -- do not call */ + /** + * Returns true if the given resource is included from some other layout in the + * project + * + * @param included the resource to check + * @return true if the file is included by some other layout + */ + public boolean isIncluded(IResource included) { + ensureInitialized(); + String mapKey = getMapKey(included); + List<String> result = mIncludedBy.get(mapKey); + if (result == null) { + String name = getResourceName(included); + if (!name.equals(mapKey)) { + result = mIncludedBy.get(name); + } + } + + return result != null && result.size() > 0; + } + @VisibleForTesting /* package */ List<String> getIncludedBy(String included) { ensureInitialized(); @@ -395,8 +418,8 @@ public class IncludeFinder { private void scanProject() { ProjectResources resources = ResourceManager.getInstance().getProjectResources(mProject); if (resources != null) { - ProjectResourceItem[] layouts = resources.getResources(ResourceType.LAYOUT); - for (ProjectResourceItem layout : layouts) { + Collection<ResourceItem> layouts = resources.getResourceItemsOfType(LAYOUT); + for (ResourceItem layout : layouts) { List<ResourceFile> sources = layout.getSourceFileList(); for (ResourceFile source : sources) { updateFileIncludes(source, false); @@ -418,7 +441,7 @@ public class IncludeFinder { * @return true if we updated the includes for the resource file */ private boolean updateFileIncludes(ResourceFile resourceFile, boolean singleUpdate) { - ResourceType[] resourceTypes = resourceFile.getResourceTypes(); + Collection<ResourceType> resourceTypes = resourceFile.getResourceTypes(); for (ResourceType type : resourceTypes) { if (type == ResourceType.LAYOUT) { ensureInitialized(); @@ -665,7 +688,7 @@ public class IncludeFinder { // /res/layout/foo.xml => "foo" // /res/layout-land/foo.xml => "-land/foo" - if (FD_LAYOUT.equals(folderName)) { + if (FD_RES_LAYOUT.equals(folderName)) { // Normal case -- keep just the basename return name; } else { @@ -912,7 +935,7 @@ public class IncludeFinder { public IFile getFile() { String reference = mId; if (!reference.contains(WS_SEP)) { - reference = SdkConstants.FD_LAYOUT + WS_SEP + reference; + reference = FD_RES_LAYOUT + WS_SEP + reference; } String projectPath = SdkConstants.FD_RESOURCES + WS_SEP + reference + '.' + EXT_XML; @@ -998,5 +1021,71 @@ public class IncludeFinder { public static Reference create(IFile file) { return new Reference(file.getProject(), getMapKey(file)); } + + /** + * Returns the resource name of this layout, such as {@code @layout/foo}. + * + * @return the resource name + */ + public String getResourceName() { + return '@' + FD_RES_LAYOUT + '/' + getName(); + } + } + + /** + * Returns a collection of layouts (expressed as resource names, such as + * {@code @layout/foo} which would be invalid includes in the given layout + * (because it would introduce a cycle) + * + * @param layout the layout file to check for cyclic dependencies from + * @return a collection of layout resources which cannot be included from + * the given layout, never null + */ + public Collection<String> getInvalidIncludes(IFile layout) { + IProject project = layout.getProject(); + Reference self = Reference.create(layout); + + // Add anyone who transitively can reach this file via includes. + LinkedList<Reference> queue = new LinkedList<Reference>(); + List<Reference> invalid = new ArrayList<Reference>(); + queue.add(self); + invalid.add(self); + Set<String> seen = new HashSet<String>(); + seen.add(self.getId()); + while (!queue.isEmpty()) { + Reference reference = queue.removeFirst(); + String refId = reference.getId(); + + // Look up both configuration specific includes as well as includes in the + // base versions + List<String> included = getIncludedBy(refId); + if (refId.indexOf('/') != -1) { + List<String> baseIncluded = getIncludedBy(reference.getName()); + if (included == null) { + included = baseIncluded; + } else if (baseIncluded != null) { + included = new ArrayList<String>(included); + included.addAll(baseIncluded); + } + } + + if (included != null && included.size() > 0) { + for (String id : included) { + if (!seen.contains(id)) { + seen.add(id); + Reference ref = new Reference(project, id); + invalid.add(ref); + queue.addLast(ref); + } + } + } + } + + List<String> result = new ArrayList<String>(); + for (Reference reference : invalid) { + result.add(reference.getResourceName()); + } + + return result; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java index 5f2450c..0dcd83e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java @@ -15,10 +15,14 @@ */ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; + import com.android.ide.common.api.MenuAction; import com.android.ide.common.api.MenuAction.OrderedChoices; import com.android.ide.common.api.MenuAction.Separator; import com.android.ide.common.api.MenuAction.Toggle; +import com.android.ide.common.layout.BaseViewRule; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; @@ -116,25 +120,58 @@ public class LayoutActionBar extends Composite { } List<MenuAction> actions = new ArrayList<MenuAction>(); engine.callAddLayoutActions(actions, parent, selectedNodes); - addActions(actions); + + // Place actions in the correct order (the actions may come from different + // rules and should be merged properly via sorting keys) + Collections.sort(actions); + + // Add in actions for the child as well, if there is exactly one. + // These are not merged into the parent list of actions; they are appended + // at the end. + int index = -1; + String label = null; + if (selectedNodes.size() == 1) { + List<MenuAction> itemActions = new ArrayList<MenuAction>(); + NodeProxy selectedNode = selectedNodes.get(0); + engine.callAddLayoutActions(itemActions, selectedNode, null); + if (itemActions.size() > 0) { + Collections.sort(itemActions); + + if (!(itemActions.get(0) instanceof MenuAction.Separator)) { + actions.add(MenuAction.createSeparator(0)); + } + label = selectedNode.getStringAttr(ANDROID_URI, ATTR_ID); + if (label != null) { + label = BaseViewRule.stripIdPrefix(label); + index = actions.size(); + } + actions.addAll(itemActions); + } + } + + addActions(actions, index, label); mLayoutToolBar.pack(); mLayoutToolBar.layout(); } - private void addActions(List<MenuAction> actions) { + private void addActions(List<MenuAction> actions, int labelIndex, String label) { if (actions.size() > 0) { - // Place actions in the correct order (the actions may come from different - // rules and should be merged properly via sorting keys) - Collections.sort(actions); - // Flag used to indicate that if there are any actions -after- this, it // should be separated from this current action (we don't unconditionally // add a separator at the end of these groups in case there are no more // actions at the end so that we don't have a trailing separator) boolean needSeparator = false; - for (final MenuAction action : actions) { + int index = 0; + for (MenuAction action : actions) { + if (index == labelIndex) { + final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH); + button.setText(label); + needSeparator = false; + } + index++; + if (action instanceof Separator) { addSeparator(mLayoutToolBar); needSeparator = false; @@ -145,7 +182,7 @@ public class LayoutActionBar extends Composite { } if (action instanceof MenuAction.OrderedChoices) { - final MenuAction.OrderedChoices choices = (OrderedChoices) action; + MenuAction.OrderedChoices choices = (OrderedChoices) action; if (!choices.isRadio()) { addDropdown(choices); } else { @@ -339,7 +376,7 @@ public class LayoutActionBar extends Composite { mZoomFitButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - rescaleToFit(); + rescaleToFit(true); } }); @@ -431,8 +468,8 @@ public class LayoutActionBar extends Composite { /** * Reset the canvas scale to best fit (so content is as large as possible without scrollbars) */ - void rescaleToFit() { - mEditor.getCanvasControl().setFitScale(); + void rescaleToFit(boolean onlyZoomOut) { + mEditor.getCanvasControl().setFitScale(onlyZoomOut); } boolean rescaleToReal(boolean real) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index 289831f..c87b669 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -64,6 +64,8 @@ import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MenuDetectEvent; +import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; @@ -212,6 +214,11 @@ public class LayoutCanvas extends Canvas { private final GestureManager mGestureManager = new GestureManager(this); /** + * When set, performs a zoom-to-fit when the next rendering image arrives. + */ + private boolean mZoomFitNextImage; + + /** * Native clipboard support. */ private ClipboardSupport mClipboardSupport; @@ -235,13 +242,15 @@ public class LayoutCanvas extends Canvas { if (zoom != null) { try { double initialScale = Double.parseDouble(zoom); - if (initialScale > 0.0) { + if (initialScale > 0.1) { mHScale.setScale(initialScale); mVScale.setScale(initialScale); } } catch (NumberFormatException nfe) { // Ignore - use zoom=100% } + } else { + mZoomFitNextImage = true; } } @@ -252,7 +261,7 @@ public class LayoutCanvas extends Canvas { // --- Set up graphic overlays // mOutlineOverlay and mEmptyOverlay are initialized lazily - mHoverOverlay = new HoverOverlay(mHScale, mVScale); + mHoverOverlay = new HoverOverlay(this, mHScale, mVScale); mHoverOverlay.create(display); mSelectionOverlay = new SelectionOverlay(this); mSelectionOverlay.create(display); @@ -286,6 +295,8 @@ public class LayoutCanvas extends Canvas { // handle backspace for other platforms as well. if (e.keyCode == SWT.BS) { mDeleteAction.run(); + } else if (e.keyCode == SWT.ESC) { + mSelectionManager.selectParent(); } else { // Zooming actions char c = e.character; @@ -294,7 +305,10 @@ public class LayoutCanvas extends Canvas { if (c == '1' && actionBar.isZoomingAllowed()) { setScale(1, true); } else if (c == '0' && actionBar.isZoomingAllowed()) { - setFitScale(); + setFitScale(true); + } else if (e.keyCode == '0' && (e.stateMask & SWT.MOD2) != 0 + && actionBar.isZoomingAllowed()) { + setFitScale(false); } else if (c == '+' && actionBar.isZoomingAllowed()) { actionBar.rescale(1); } else if (c == '-' && actionBar.isZoomingAllowed()) { @@ -425,7 +439,7 @@ public class LayoutCanvas extends Canvas { /** * Returns the {@link LayoutEditor} associated with this canvas. */ - /* package */ LayoutEditor getLayoutEditor() { + LayoutEditor getLayoutEditor() { return mLayoutEditor; } @@ -519,6 +533,11 @@ public class LayoutCanvas extends Canvas { return mClipboardSupport; } + /** Returns the Select All action bound to this canvas */ + Action getSelectAllAction() { + return mSelectAllAction; + } + /** * Sets the result of the layout rendering. The result object indicates if the layout * rendering succeeded. If it did, it contains a bitmap and the objects rectangles. @@ -534,11 +553,12 @@ public class LayoutCanvas extends Canvas { * {@link #showInvisibleViews(boolean)}) where individual invisible nodes * are padded during certain interactions. */ - /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes) { + /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, + boolean layoutlib5) { // disable any hover clearHover(); - mViewHierarchy.setSession(session, explodedNodes); + mViewHierarchy.setSession(session, explodedNodes, layoutlib5); if (mViewHierarchy.isValid() && session != null) { Image image = mImageOverlay.setImage(session.getImage(), session.isAlphaChannelImage()); @@ -547,6 +567,16 @@ public class LayoutCanvas extends Canvas { if (image != null) { mHScale.setSize(image.getImageData().width, getClientArea().width); mVScale.setSize(image.getImageData().height, getClientArea().height); + if (mZoomFitNextImage) { + mZoomFitNextImage = false; + // Must be run asynchronously because getClientArea() returns 0 bounds + // when the editor is being initialized + getDisplay().asyncExec(new Runnable() { + public void run() { + setFitScale(true); + } + }); + } } } @@ -563,6 +593,10 @@ public class LayoutCanvas extends Canvas { } /* package */ void setScale(double scale, boolean redraw) { + if (scale <= 0.0) { + scale = 1.0; + } + if (scale == getScale()) { return; } @@ -578,8 +612,14 @@ public class LayoutCanvas extends Canvas { AdtPlugin.setFileProperty(mLayoutEditor.getInputFile(), NAME_ZOOM, zoomValue); } - /** Scales the canvas to best fit */ - void setFitScale() { + /** + * Scales the canvas to best fit + * + * @param onlyZoomOut if true, then the zooming factor will never be larger than 1, + * which means that this function will zoom out if necessary to show the + * rendered image, but it will never zoom in. + */ + void setFitScale(boolean onlyZoomOut) { Image image = getImageOverlay().getImage(); if (image != null) { Rectangle canvasSize = getClientArea(); @@ -610,10 +650,15 @@ public class LayoutCanvas extends Canvas { vMargin = vDelta / 2; } - double hScale = canvasWidth / (double) (sceneWidth - hMargin); - double vScale = canvasHeight / (double) (sceneHeight - vMargin); + double hScale = (canvasWidth - 2 * hMargin) / (double) sceneWidth; + double vScale = (canvasHeight - 2 * vMargin) / (double) sceneHeight; double scale = Math.min(hScale, vScale); + + if (onlyZoomOut) { + scale = Math.min(1.0, scale); + } + setScale(scale, true); } } @@ -724,6 +769,7 @@ public class LayoutCanvas extends Canvas { if (mShowInvisible == show) { return; } + mShowInvisible = show; // Optimization: Avoid doing work when we don't have invisible parents (on show) // or formerly exploded nodes (on hide). @@ -733,7 +779,6 @@ public class LayoutCanvas extends Canvas { return; } - mShowInvisible = show; mLayoutEditor.recomputeLayout(); } @@ -841,10 +886,23 @@ public class LayoutCanvas extends Canvas { private void showInclude(String url) { GraphicalEditorPart graphicalEditor = mLayoutEditor.getGraphicalEditor(); IPath filePath = graphicalEditor.findResourceFile(url); + if (filePath == null) { + // Should not be possible - if the URL had been bad, then we wouldn't + // have been able to render the scene and you wouldn't have been able + // to click on it + return; + } + + // Save the including file, if necessary: without it, the "Show Included In" + // facility which is invoked automatically will not work properly if the <include> + // tag is not in the saved version of the file, since the outer file is read from + // disk rather than from memory. + IEditorSite editorSite = graphicalEditor.getEditorSite(); + IWorkbenchPage page = editorSite.getPage(); + page.saveEditor(mLayoutEditor, false); IWorkspaceRoot workspace = ResourcesPlugin.getWorkspace().getRoot(); IPath workspacePath = workspace.getLocation(); - IEditorSite editorSite = graphicalEditor.getEditorSite(); if (workspacePath.isPrefixOf(filePath)) { IPath relativePath = filePath.makeRelativeTo(workspacePath); IResource xmlFile = workspace.findMember(relativePath); @@ -899,7 +957,6 @@ public class LayoutCanvas extends Canvas { IFileStore fileStore = EFS.getLocalFileSystem().getStore(filePath); // fileStore = fileStore.getChild(names[i]); if (!fileStore.fetchInfo().isDirectory() && fileStore.fetchInfo().exists()) { - IWorkbenchPage page = editorSite.getWorkbenchWindow().getActivePage(); try { IDE.openEditorOnFileStore(page, fileStore); return; @@ -919,7 +976,8 @@ public class LayoutCanvas extends Canvas { /** * Returns the layout resource name of this layout - * @return + * + * @return the layout resource name of this layout */ public String getLayoutResourceName() { GraphicalEditorPart graphicalEditor = mLayoutEditor.getGraphicalEditor(); @@ -1158,6 +1216,16 @@ public class LayoutCanvas extends Canvas { new DynamicContextMenu(mLayoutEditor, this, mMenuManager); Menu menu = mMenuManager.createContextMenu(this); setMenu(menu); + + // Add listener to detect when the menu is about to be posted, such that + // we can sync the selection. Without this, you can right click on something + // in the canvas which is NOT selected, and the context menu will show items related + // to the selection, NOT the item you clicked on!! + addMenuDetectListener(new MenuDetectListener() { + public void menuDetected(MenuDetectEvent e) { + mSelectionManager.menuClick(e); + } + }); } /** @@ -1173,15 +1241,13 @@ public class LayoutCanvas extends Canvas { private void setupStaticMenuActions(IMenuManager manager) { manager.removeAll(); + manager.add(new SelectionManager.SelectionMenu(mLayoutEditor.getGraphicalEditor())); + manager.add(new Separator()); manager.add(mCutAction); manager.add(mCopyAction); manager.add(mPasteAction); - manager.add(new Separator()); - manager.add(mDeleteAction); - manager.add(mSelectAllAction); - manager.add(new Separator()); manager.add(new PlayAnimationMenu(this)); manager.add(new Separator()); @@ -1252,7 +1318,7 @@ public class LayoutCanvas extends Canvas { // A root node requires the Android XMLNS uiNew.setAttributeValue( - LayoutConstants.ANDROID_NS_PREFIX, + LayoutConstants.ANDROID_NS_NAME, XmlnsAttributeDescriptor.XMLNS_URI, SdkConstants.NS_RESOURCES, true /*override*/); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java new file mode 100644 index 0000000..2bfa841 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.EXPANDABLE_LIST_VIEW; +import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("restriction") // XML DOM model +public class LayoutMetadata { + /** The default layout to use for list items in expandable list views */ + public static final String DEFAULT_EXPANDABLE_LIST_ITEM = "simple_expandable_list_item_2"; //$NON-NLS-1$ + /** The default layout to use for list items in plain list views */ + public static final String DEFAULT_LIST_ITEM = "simple_list_item_2"; //$NON-NLS-1$ + + /** The string to start metadata comments with */ + private static final String COMMENT_PROLOGUE = " Preview: "; + /** The string to end metadata comments with */ + private static final String COMMENT_EPILOGUE = " "; + /** The property key, included in comments, which references a list item layout */ + public static final String KEY_LV_ITEM = "listitem"; //$NON-NLS-1$ + /** The property key, included in comments, which references a list header layout */ + public static final String KEY_LV_HEADER = "listheader"; //$NON-NLS-1$ + /** The property key, included in comments, which references a list footer layout */ + public static final String KEY_LV_FOOTER = "listfooter"; //$NON-NLS-1$ + + /** The metadata class is a singleton for now since it has no state of its own */ + private static final LayoutMetadata sInstance = new LayoutMetadata(); + + /** Do not use -- use factory instead */ + private LayoutMetadata() { + } + + /** + * Return the {@link LayoutMetadata} instance + * + * @return the {@link LayoutMetadata} instance + */ + public static LayoutMetadata get() { + return sInstance; + } + + /** + * Returns the given property of the given DOM node, or null + * + * @param document the document to look up and read lock the model for + * @param node the XML node to associate metadata with + * @param name the name of the property to look up + * @return the value stored with the given node and name, or null + */ + public String getProperty(IDocument document, Node node, String name) { + IStructuredModel model = null; + try { + IModelManager modelManager = StructuredModelManager.getModelManager(); + model = modelManager.getExistingModelForRead(document); + + Node comment = findComment(node); + if (comment != null) { + String text = comment.getNodeValue(); + assert text.startsWith(COMMENT_PROLOGUE); + String valuesString = text.substring(COMMENT_PROLOGUE.length()); + String[] values = valuesString.split(","); //$NON-NLS-1$ + if (values.length == 1) { + valuesString = values[0].trim(); + if (valuesString.indexOf('\n') != -1) { + values = valuesString.split("\n"); //$NON-NLS-1$ + } + } + String target = name + '='; + for (int j = 0; j < values.length; j++) { + String value = values[j].trim(); + if (value.startsWith(target)) { + return value.substring(target.length()).trim(); + } + } + } + + return null; + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + } + + /** + * Sets the given property of the given DOM node to a given value, or if null clears + * the property. + * + * @param document the document to look up and write lock the model for + * @param node the XML node to associate metadata with + * @param name the name of the property to set + * @param value the value to store for the given node and name, or null to remove it + */ + public void setProperty(IDocument document, Node node, String name, String value) { + // Reserved characters: [,-=] + assert name.indexOf('-') == -1; + assert value == null || value.indexOf('-') == -1; + assert name.indexOf(',') == -1; + assert value == null || value.indexOf(',') == -1; + assert name.indexOf('=') == -1; + assert value == null || value.indexOf('=') == -1; + + IStructuredModel model = null; + try { + IModelManager modelManager = StructuredModelManager.getModelManager(); + model = modelManager.getExistingModelForEdit(document); + if (model instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) model; + Document domDocument = domModel.getDocument(); + assert node.getOwnerDocument() == domDocument; + } + + Document doc = node.getOwnerDocument(); + Node commentNode = findComment(node); + + String commentText = null; + if (commentNode != null) { + String text = commentNode.getNodeValue(); + assert text.startsWith(COMMENT_PROLOGUE); + String valuesString = text.substring(COMMENT_PROLOGUE.length()); + String[] values = valuesString.split(","); //$NON-NLS-1$ + if (values.length == 1) { + valuesString = values[0].trim(); + if (valuesString.indexOf('\n') != -1) { + values = valuesString.split("\n"); //$NON-NLS-1$ + } + } + String target = name + '='; + List<String> preserve = new ArrayList<String>(); + for (int j = 0; j < values.length; j++) { + String v = values[j].trim(); + if (v.length() == 0) { + continue; + } + if (!v.startsWith(target)) { + preserve.add(v.trim()); + } + } + if (value != null) { + preserve.add(name + '=' + value.trim()); + } + if (preserve.size() > 0) { + if (preserve.size() > 1) { + Collections.sort(preserve); + String firstLineIndent = AndroidXmlEditor.getIndent(document, commentNode); + String oneIndentLevel = " "; //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(); + sb.append(COMMENT_PROLOGUE); + sb.append('\n'); + for (String s : preserve) { + sb.append(firstLineIndent); + sb.append(oneIndentLevel); + sb.append(s); + sb.append('\n'); + } + sb.append(firstLineIndent); + sb.append(COMMENT_EPILOGUE); + commentText = sb.toString(); + } else { + commentText = COMMENT_PROLOGUE + preserve.get(0) + COMMENT_EPILOGUE; + } + } + } else if (value != null) { + commentText = COMMENT_PROLOGUE + name + '=' + value + COMMENT_EPILOGUE; + } + + if (commentText == null) { + if (commentNode != null) { + // Remove the comment, along with surrounding whitespace if applicable + Node previous = commentNode.getPreviousSibling(); + if (previous != null && previous.getNodeType() == Node.TEXT_NODE) { + String text = previous.getNodeValue(); + if (text.trim().length() == 0) { + node.removeChild(previous); + } + } + node.removeChild(commentNode); + Node first = node.getFirstChild(); + if (first != null && first.getNextSibling() == null + && first.getNodeType() == Node.TEXT_NODE) { + String text = first.getNodeValue(); + if (text.trim().length() == 0) { + node.removeChild(first); + } + } + } + return; + } + + if (commentNode != null) { + commentNode.setNodeValue(commentText); + } else { + commentNode = doc.createComment(commentText); + String firstLineIndent = AndroidXmlEditor.getIndent(document, node); + Node firstChild = node.getFirstChild(); + boolean indentAfter = firstChild == null + || firstChild.getNodeType() != Node.TEXT_NODE + || firstChild.getNodeValue().indexOf('\n') == -1; + String oneIndentLevel = " "; //$NON-NLS-1$ + node.insertBefore(doc.createTextNode('\n' + firstLineIndent + oneIndentLevel), + firstChild); + node.insertBefore(commentNode, firstChild); + if (indentAfter) { + node.insertBefore(doc.createTextNode('\n' + firstLineIndent), firstChild); + } + } + } finally { + if (model != null) { + model.releaseFromEdit(); + } + } + } + + /** Finds the comment node associated with the given node, or null if not found */ + private Node findComment(Node node) { + NodeList children = node.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.COMMENT_NODE) { + String text = child.getNodeValue(); + if (text.startsWith(COMMENT_PROLOGUE)) { + return child; + } + } + } + + return null; + } + + /** + * Returns the given property of the given DOM node, or null + * + * @param editor the editor associated with the property + * @param node the XML node to associate metadata with + * @param name the name of the property to look up + * @return the value stored with the given node and name, or null + */ + public String getProperty(AndroidXmlEditor editor, Node node, String name) { + IDocument document = editor.getStructuredSourceViewer().getDocument(); + return getProperty(document, node, name); + } + + /** + * Sets the given property of the given DOM node to a given value, or if null clears + * the property. + * + * @param editor the editor associated with the property + * @param node the XML node to associate metadata with + * @param name the name of the property to set + * @param value the value to store for the given node and name, or null to remove it + */ + public void setProperty(AndroidXmlEditor editor, Node node, String name, String value) { + IDocument document = editor.getStructuredSourceViewer().getDocument(); + setProperty(document, node, name, value); + } + + /** Strips out @layout/ or @android:layout/ from the given layout reference */ + private static String stripLayoutPrefix(String layout) { + if (layout.startsWith(ANDROID_LAYOUT_PREFIX)) { + layout = layout.substring(ANDROID_LAYOUT_PREFIX.length()); + } else if (layout.startsWith(LAYOUT_PREFIX)) { + layout = layout.substring(LAYOUT_PREFIX.length()); + } + + return layout; + } + + /** + * Creates an {@link AdapterBinding} for the given view object, or null if the user + * has not yet chosen a target layout to use for the given AdapterView. + * + * @param viewObject the view object to create an adapter binding for + * @param uiNode the ui node corresponding to the view object + * @return a binding, or null + */ + public AdapterBinding getNodeBinding(Object viewObject, UiViewElementNode uiNode) { + AndroidXmlEditor editor = uiNode.getEditor(); + if (editor != null) { + Node xmlNode = uiNode.getXmlNode(); + + String header = getProperty(editor, xmlNode, KEY_LV_HEADER); + String footer = getProperty(editor, xmlNode, KEY_LV_FOOTER); + String layout = getProperty(editor, xmlNode, KEY_LV_ITEM); + if (layout != null || header != null || footer != null) { + AdapterBinding binding = new AdapterBinding(12); + + if (header != null) { + boolean isFramework = header.startsWith(ANDROID_LAYOUT_PREFIX); + binding.addHeader(new ResourceReference(stripLayoutPrefix(header), + isFramework)); + } + + if (footer != null) { + boolean isFramework = footer.startsWith(ANDROID_LAYOUT_PREFIX); + binding.addFooter(new ResourceReference(stripLayoutPrefix(footer), + isFramework)); + } + + if (layout != null) { + boolean isFramework = layout.startsWith(ANDROID_LAYOUT_PREFIX); + if (isFramework) { + layout = layout.substring(ANDROID_LAYOUT_PREFIX.length()); + } else if (layout.startsWith(LAYOUT_PREFIX)) { + layout = layout.substring(LAYOUT_PREFIX.length()); + } + + binding.addItem(new DataBindingItem(layout, isFramework, 1)); + } else if (viewObject != null) { + String listFqcn = ProjectCallback.getListViewFqcn(viewObject.getClass()); + if (listFqcn != null) { + if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) { + binding.addItem( + new DataBindingItem(DEFAULT_EXPANDABLE_LIST_ITEM, + true /* isFramework */, 1)); + } else { + binding.addItem( + new DataBindingItem(DEFAULT_LIST_ITEM, + true /* isFramework */, 1)); + } + } + } else { + binding.addItem( + new DataBindingItem(DEFAULT_LIST_ITEM, + true /* isFramework */, 1)); + } + return binding; + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java new file mode 100644 index 0000000..e522957 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_LAYOUT_PREFIX; +import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_LV_FOOTER; +import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_LV_HEADER; +import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_LV_ITEM; + +import com.android.ide.common.rendering.api.Capability; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.ui.ResourceChooser; +import com.android.resources.ResourceType; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.w3c.dom.Node; + +/** + * "Preview List Content" context menu which lists available data types and layouts + * the user can choose to view the ListView as. + */ +public class ListViewTypeMenu extends SubmenuAction { + /** Associated canvas */ + private final LayoutCanvas mCanvas; + + /** + * Creates a "Preview List Content" menu + * + * @param canvas associated canvas + */ + public ListViewTypeMenu(LayoutCanvas canvas) { + super("Preview List Content"); + mCanvas = canvas; + } + + @Override + protected void addMenuItems(Menu menu) { + GraphicalEditorPart graphicalEditor = mCanvas.getLayoutEditor().getGraphicalEditor(); + if (graphicalEditor.renderingSupports(Capability.ADAPTER_BINDING)) { + IAction action = new PickLayoutAction("Choose Layout...", KEY_LV_ITEM); + new ActionContributionItem(action).fill(menu, -1); + new Separator().fill(menu, -1); + + String selected = getSelectedLayout(); + if (selected != null) { + if (selected.startsWith(ANDROID_LAYOUT_PREFIX)) { + selected = selected.substring(ANDROID_LAYOUT_PREFIX.length()); + } + } + action = new SetListTypeAction("Simple List Item", + "simple_list_item_1", selected); //$NON-NLS-1$ + new ActionContributionItem(action).fill(menu, -1); + action = new SetListTypeAction("Simple 2-Line List Item", + "simple_list_item_2", //$NON-NLS-1$ + selected); + new ActionContributionItem(action).fill(menu, -1); + action = new SetListTypeAction("Checked List Item", + "simple_list_item_checked", //$NON-NLS-1$ + selected); + new ActionContributionItem(action).fill(menu, -1); + action = new SetListTypeAction("Single Choice List Item", + "simple_list_item_single_choice", //$NON-NLS-1$ + selected); + new ActionContributionItem(action).fill(menu, -1); + action = new SetListTypeAction("Multiple Choice List Item", + "simple_list_item_multiple_choice", //$NON-NLS-1$ + selected); + new Separator().fill(menu, -1); + action = new SetListTypeAction("Simple Expandable List Item", + "simple_expandable_list_item_1", selected); //$NON-NLS-1$ + new ActionContributionItem(action).fill(menu, -1); + action = new SetListTypeAction("Simple 2-Line Expandable List Item", + "simple_expandable_list_item_2", //$NON-NLS-1$ + selected); + new ActionContributionItem(action).fill(menu, -1); + + new Separator().fill(menu, -1); + action = new PickLayoutAction("Choose Header...", KEY_LV_HEADER); + new ActionContributionItem(action).fill(menu, -1); + action = new PickLayoutAction("Choose Footer...", KEY_LV_FOOTER); + new ActionContributionItem(action).fill(menu, -1); + + } else { + // Should we just hide the menu item instead? + addDisabledMessageItem( + "Not supported for this SDK version; try changing the Render Target"); + } + } + + private class SetListTypeAction extends Action { + private final String mLayout; + + public SetListTypeAction(String title, String layout, String selected) { + super(title, IAction.AS_RADIO_BUTTON); + mLayout = layout; + + if (layout.equals(selected)) { + setChecked(true); + } + } + + @Override + public void run() { + setNewType(KEY_LV_ITEM, ANDROID_LAYOUT_PREFIX + mLayout); + } + } + + /** + * Action which brings up the "Create new XML File" wizard, pre-selected with the + * animation category + */ + private class PickLayoutAction extends Action { + private final String mType; + + public PickLayoutAction(String title, String type) { + super(title, IAction.AS_PUSH_BUTTON); + mType = type; + } + + @Override + public void run() { + LayoutEditor editor = mCanvas.getLayoutEditor(); + IProject project = editor.getProject(); + // get the resource repository for this project and the system resources. + ResourceRepository projectRepository = ResourceManager.getInstance() + .getProjectResources(project); + Shell shell = mCanvas.getShell(); + + AndroidTargetData data = editor.getTargetData(); + ResourceRepository systemRepository = data.getFrameworkResources(); + + ResourceChooser dlg = new ResourceChooser(project, + ResourceType.LAYOUT, projectRepository, + systemRepository, shell); + + IInputValidator validator = CyclicDependencyValidator.create(editor.getInputFile()); + + if (validator != null) { + // Ensure wide enough to accommodate validator error message + dlg.setSize(70, 10); + dlg.setInputValidator(validator); + } + + String layout = getSelectedLayout(); + if (layout != null) { + dlg.setCurrentResource(layout); + } + + int result = dlg.open(); + if (result == ResourceChooser.CLEAR_RETURN_CODE) { + setNewType(mType, null); + } else if (result == Window.OK) { + String newType = dlg.getCurrentResource(); + setNewType(mType, newType); + } + } + } + + private String getSelectedLayout() { + String layout = null; + LayoutEditor editor = mCanvas.getLayoutEditor(); + LayoutMetadata metadata = LayoutMetadata.get(); + SelectionManager selectionManager = mCanvas.getSelectionManager(); + for (SelectionItem item : selectionManager.getSelections()) { + UiViewElementNode node = item.getViewInfo().getUiViewNode(); + + Node xmlNode = node.getXmlNode(); + layout = metadata.getProperty(editor, xmlNode, KEY_LV_ITEM); + if (layout != null) { + return layout; + } + } + return null; + } + + public void setNewType(String type, String layout) { + LayoutEditor editor = mCanvas.getLayoutEditor(); + GraphicalEditorPart graphicalEditor = editor.getGraphicalEditor(); + LayoutMetadata metadata = LayoutMetadata.get(); + SelectionManager selectionManager = mCanvas.getSelectionManager(); + + for (SelectionItem item : selectionManager.getSelections()) { + UiViewElementNode node = item.getViewInfo().getUiViewNode(); + Node xmlNode = node.getXmlNode(); + metadata.setProperty(editor, xmlNode, type, layout); + } + + // Refresh + graphicalEditor.recomputeLayout(); + mCanvas.redraw(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java index 07ab41c..a2cdbe6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java @@ -24,6 +24,9 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.TransferData; @@ -120,6 +123,15 @@ public class MoveGesture extends DropGesture { return Collections.<Overlay> singletonList(mOverlay); } + @Override + public void end(ControlPoint pos, boolean canceled) { + super.end(pos, canceled); + + // Ensure that the outline is back to showing the current selection, since during + // a drag gesture we temporarily set it to show the current target node instead. + mCanvas.getSelectionManager().syncOutlineSelection(); + } + /* * The cursor has entered the drop target boundaries. * {@inheritDoc} @@ -658,6 +670,26 @@ public class MoveGesture extends DropGesture { if (needRedraw || (mFeedback != null && mFeedback.requestPaint)) { mCanvas.redraw(); } + + // Update outline to show the target node there + OutlinePage outline = mCanvas.getOutlinePage(); + TreeSelection newSelection = TreeSelection.EMPTY; + if (mCurrentView != null) { + // Find the view corresponding to the target node. The current view can be a leaf + // view whereas the target node is always a parent layout. + if (mCurrentView.getUiViewNode() != mTargetNode.getNode()) { + mCurrentView = mCurrentView.getParent(); + } + if (mCurrentView != null && mCurrentView.getUiViewNode() == mTargetNode.getNode()) { + TreePath treePath = SelectionManager.getTreePath(mCurrentView); + newSelection = new TreeSelection(treePath); + } + } + + ISelection currentSelection = outline.getSelection(); + if (currentSelection == null || !currentSelection.equals(newSelection)) { + outline.setSelection(newSelection); + } } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java index f591149..d1ca8a9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlineDropListener.java @@ -155,7 +155,7 @@ import java.util.Set; } // Select the newly dropped nodes final SelectionManager selectionManager = canvas.getSelectionManager(); - selectionManager.updateOutlineSelection(added); + selectionManager.setOutlineSelection(added); canvas.redraw(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java index 02f98b1..9c27f6e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java @@ -17,11 +17,13 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX; import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX; - +import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL; import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; import com.android.annotations.VisibleForTesting; @@ -469,7 +471,20 @@ public class OutlinePage extends ContentOutlinePage UiElementNode node = (UiElementNode) element; ElementDescriptor desc = node.getDescriptor(); if (desc != null) { - Image img = desc.getIcon(); + Image img = null; + // Special case for the common case of vertical linear layouts: + // show vertical linear icon (the default icon shows horizontal orientation) + if (desc.getUiName().equals(LINEAR_LAYOUT)) { + Element e = (Element) node.getXmlNode(); + if (VALUE_VERTICAL.equals(e.getAttributeNS(ANDROID_URI, + ATTR_ORIENTATION))) { + IconFactory factory = IconFactory.getInstance(); + img = factory.getIcon("VerticalLinearLayout"); //$NON-NLS-1$ + } + } + if (img == null) { + img = desc.getGenericIcon(); + } if (img != null) { if (node.hasError()) { return new ErrorImageComposite(img).createImage(); @@ -589,6 +604,8 @@ public class OutlinePage extends ContentOutlinePage mMenuManager.add(mMoveDownAction); mMenuManager.add(new Separator()); + mMenuManager.add(new SelectionManager.SelectionMenu(mGraphicalEditorPart)); + mMenuManager.add(new Separator()); final String prefix = LayoutCanvas.PREFIX_CANVAS_ACTION; mMenuManager.add(new DelegateAction(prefix + ActionFactory.CUT.getId())); mMenuManager.add(new DelegateAction(prefix + ActionFactory.COPY.getId())); @@ -597,7 +614,6 @@ public class OutlinePage extends ContentOutlinePage mMenuManager.add(new Separator()); mMenuManager.add(new DelegateAction(prefix + ActionFactory.DELETE.getId())); - mMenuManager.add(new DelegateAction(prefix + ActionFactory.SELECT_ALL.getId())); mMenuManager.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { @@ -790,7 +806,7 @@ public class OutlinePage extends ContentOutlinePage } } - selectionManager.updateOutlineSelection(added); + selectionManager.setOutlineSelection(added); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java index f8b3109..2227531 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java @@ -23,24 +23,29 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.Rect; import com.android.ide.common.api.MenuAction.Toggle; +import com.android.ide.common.api.Rect; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.Capability; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderSession; -import com.android.ide.common.rendering.api.ViewInfo; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite; import com.android.ide.eclipse.adt.internal.editors.ui.IDecorContent; @@ -50,7 +55,6 @@ import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkConstants; import com.android.util.Pair; import org.eclipse.jface.action.Action; @@ -69,8 +73,11 @@ import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; +import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; @@ -79,10 +86,15 @@ import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -91,6 +103,7 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -123,7 +136,7 @@ public class PaletteControl extends Composite { */ public static class PaletteDecor implements IDecorContent { private final GraphicalEditorPart mEditorPart; - private Control mControl; + private PaletteControl mControl; public PaletteDecor(GraphicalEditorPart editor) { mEditorPart = editor; @@ -144,6 +157,22 @@ public class PaletteControl extends Composite { public Control getControl() { return mControl; } + + public void createToolbarItems(final ToolBar toolbar) { + final ToolItem popupMenuItem = new ToolItem(toolbar, SWT.PUSH); + popupMenuItem.setToolTipText("View Menu"); + popupMenuItem.setImage(IconFactory.getInstance().getIcon("view_menu")); + popupMenuItem.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + Rectangle bounds = popupMenuItem.getBounds(); + // Align menu horizontally with the toolbar button and + // vertically with the bottom of the toolbar + Point point = toolbar.toDisplay(bounds.x, bounds.y + bounds.height); + mControl.showMenu(point.x, point.y); + } + }); + } } /** @@ -417,7 +446,17 @@ public class PaletteControl extends Composite { categoryToItems.put(category, categoryItems); } - if (expandedCategories == null && headers.size() > 0) { + headers.add("Custom & Library Views"); + + // Set the categories to expand the first item if + // (1) we don't have a previously selected category, or + // (2) there's just one category anyway, or + // (3) the set of categories have changed so our previously selected category + // doesn't exist anymore (can happen when you toggle "Show Categories") + if ((expandedCategories == null && headers.size() > 0) || headers.size() == 1 || + (expandedCategories != null && expandedCategories.size() >= 1 + && !headers.contains( + expandedCategories.iterator().next().replace("&&", "&")))) { //$NON-NLS-1$ //$NON-NLS-2$ // Expand the first category if we don't have a previous selection (e.g. refresh) expandedCategories = Collections.singleton(headers.get(0)); } @@ -430,10 +469,45 @@ public class PaletteControl extends Composite { mAccordion = new AccordionControl(this, SWT.NONE, headers, fillVertical, wrap, expandedCategories) { @Override - protected Composite createChildContainer(Composite parent) { - Composite composite = super.createChildContainer(parent); - if (mPaletteMode.isPreview() && mBackground != null) { - composite.setBackground(mBackground); + protected Composite createChildContainer(Composite parent, Object header, int style) { + assert categoryToItems != null; + List<ViewElementDescriptor> list = categoryToItems.get(header); + final Composite composite; + if (list == null) { + assert header.equals("Custom & Library Views"); + + Composite wrapper = new Composite(parent, SWT.NONE); + GridLayout gridLayout = new GridLayout(1, false); + gridLayout.marginWidth = gridLayout.marginHeight = 0; + gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0; + gridLayout.marginBottom = 3; + wrapper.setLayout(gridLayout); + if (mPaletteMode.isPreview() && mBackground != null) { + wrapper.setBackground(mBackground); + } + composite = super.createChildContainer(wrapper, header, + style | SWT.NO_BACKGROUND); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + + Button refreshButton = new Button(wrapper, SWT.PUSH | SWT.FLAT); + refreshButton.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, + false, false, 1, 1)); + refreshButton.setText("Refresh"); + refreshButton.setImage(IconFactory.getInstance().getIcon("refresh")); //$NON-NLS-1$ + refreshButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + CustomViewFinder finder = CustomViewFinder.get(mEditor.getProject()); + finder.refresh(new ViewFinderListener(composite)); + } + }); + + wrapper.layout(true); + } else { + composite = super.createChildContainer(parent, header, style); + if (mPaletteMode.isPreview() && mBackground != null) { + composite.setBackground(mBackground); + } } addMenu(composite); return composite; @@ -442,8 +516,14 @@ public class PaletteControl extends Composite { protected void createChildren(Composite parent, Object header) { assert categoryToItems != null; List<ViewElementDescriptor> list = categoryToItems.get(header); - for (ViewElementDescriptor desc : list) { - createItem(parent, desc); + if (list == null) { + assert header.equals("Custom & Library Views"); + addCustomItems(parent); + return; + } else { + for (ViewElementDescriptor desc : list) { + createItem(parent, desc); + } } } }; @@ -465,6 +545,47 @@ public class PaletteControl extends Composite { layout(true); } + protected void addCustomItems(final Composite parent) { + final CustomViewFinder finder = CustomViewFinder.get(mEditor.getProject()); + Collection<String> allViews = finder.getAllViews(); + if (allViews == null) { // Not yet initialized: trigger an async refresh + finder.refresh(new ViewFinderListener(parent)); + return; + } + + // Remove previous content + for (Control c : parent.getChildren()) { + c.dispose(); + } + + // Add new views + for (final String fqcn : allViews) { + CustomViewDescriptorService service = CustomViewDescriptorService.getInstance(); + ViewElementDescriptor desc = service.getDescriptor(mEditor.getProject(), fqcn); + if (desc == null) { + // The descriptor lookup performs validation steps of the class, and may + // in some cases determine that this is not a view and will return null; + // guard against that. + continue; + } + + Control item = createItem(parent, desc); + + // Add control-click listener on custom view items to you can warp to + if (item instanceof IconTextItem) { + IconTextItem it = (IconTextItem) item; + it.addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + if ((e.stateMask & SWT.MOD1) != 0) { + AdtPlugin.openJavaClass(mEditor.getProject(), fqcn); + } + } + }); + } + } + } + /* package */ GraphicalEditorPart getEditor() { return mEditor; } @@ -525,7 +646,7 @@ public class PaletteControl extends Composite { break; } case ICON_ONLY: { - item = new ImageControl(parent, SWT.None, desc.getIcon()); + item = new ImageControl(parent, SWT.None, desc.getGenericIcon()); item.setToolTipText(desc.getUiName()); break; } @@ -559,7 +680,7 @@ public class PaletteControl extends Composite { mMouseIn = false; setText(desc.getUiName()); - setImage(desc.getIcon()); + setImage(desc.getGenericIcon()); setToolTipText(desc.getTooltip()); addMouseTrackListener(this); } @@ -613,13 +734,16 @@ public class PaletteControl extends Composite { createDragImage(e); if (mImage != null && !mIsPlaceholder) { - ImageData data = mImage.getImageData(); + int imageWidth = mImageLayoutBounds.width; + int imageHeight = mImageLayoutBounds.height; + assert mImageLayoutBounds.x == 0; + assert mImageLayoutBounds.y == 0; LayoutCanvas canvas = mEditor.getCanvasControl(); double scale = canvas.getScale(); - int x = -data.width / 2; - int y = -data.height / 2; - int width = (int) (data.width / scale); - int height = (int) (data.height / scale); + int x = -imageWidth / 2; + int y = -imageHeight / 2; + int width = (int) (imageWidth / scale); + int height = (int) (imageHeight / scale); bounds = new Rect(0, 0, width, height); dragBounds = new Rect(x, y, width, height); } @@ -629,6 +753,10 @@ public class PaletteControl extends Composite { null /* parentFqcn */, bounds /* bounds */, null /* parentBounds */); + if (mDesc instanceof PaletteMetadataDescriptor) { + PaletteMetadataDescriptor pm = (PaletteMetadataDescriptor) mDesc; + pm.initializeNew(se); + } mElements = new SimpleElement[] { se }; // Register this as the current dragged data @@ -681,8 +809,10 @@ public class PaletteControl extends Composite { /** Amount of alpha to multiply into the image (divided by 256) */ private static final int IMG_ALPHA = 216; - /** The image shown by the drag source effect */ + /** The image shown during the drag */ private Image mImage; + /** The non-effect bounds of the drag image */ + private Rectangle mImageLayoutBounds; /** * If true, the image is a preview of the view, and if not it is a "fallback" @@ -691,7 +821,14 @@ public class PaletteControl extends Composite { private boolean mIsPlaceholder; private void createDragImage(DragSourceEvent event) { - mImage = renderPreview(); + Pair<Image, Rectangle> preview = renderPreview(); + if (preview != null) { + mImage = preview.getFirst(); + mImageLayoutBounds = preview.getSecond(); + } else { + mImage = null; + mImageLayoutBounds = null; + } mIsPlaceholder = mImage == null; if (mIsPlaceholder) { @@ -737,8 +874,17 @@ public class PaletteControl extends Composite { } } - /** Performs the actual rendering of the descriptor into an image */ - private Image renderPreview() { + /** + * Performs the actual rendering of the descriptor into an image and returns the + * image as well as the layout bounds of the image (not including drop shadow etc) + */ + private Pair<Image, Rectangle> renderPreview() { + ViewMetadataRepository repository = ViewMetadataRepository.get(); + RenderMode renderMode = repository.getRenderMode(mDesc.getFullClassName()); + if (renderMode == RenderMode.SKIP) { + return null; + } + // Create blank XML document Document document = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -764,14 +910,21 @@ public class PaletteControl extends Composite { attr.setValue(ANDROID_URI); element.getAttributes().setNamedItemNS(attr); - element.setAttributeNS(SdkConstants.NS_RESOURCES, + element.setAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH, VALUE_WRAP_CONTENT); - element.setAttributeNS(SdkConstants.NS_RESOURCES, + element.setAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); // This doesn't apply to all, but doesn't seem to cause harm and makes for a // better experience with text-oriented views like buttons and texts - element.setAttributeNS(SdkConstants.NS_RESOURCES, ATTR_TEXT, mDesc.getUiName()); + element.setAttributeNS(ANDROID_URI, ATTR_TEXT, + DescriptorsUtils.getBasename(mDesc.getUiName())); + + // Is this a palette variation? + if (mDesc instanceof PaletteMetadataDescriptor) { + PaletteMetadataDescriptor pm = (PaletteMetadataDescriptor) mDesc; + pm.initializeNew(element); + } document.appendChild(element); @@ -799,7 +952,7 @@ public class PaletteControl extends Composite { UiViewElementNode childUiNode = (UiViewElementNode) child; NodeProxy childNode = nodeFactory.create(childUiNode); canvas.getRulesEngine().callCreateHooks(layoutEditor, - null, childNode, InsertType.CREATE); + null, childNode, InsertType.CREATE_PREVIEW); } Integer overrideBgColor = null; @@ -878,6 +1031,8 @@ public class PaletteControl extends Composite { } if (cropped != null) { + int width = initialCrop != null ? initialCrop.w : cropped.getWidth(); + int height = initialCrop != null ? initialCrop.h : cropped.getHeight(); boolean needsContrast = hasTransparency && !ImageUtils.containsDarkPixels(cropped); cropped = ImageUtils.createDropShadow(cropped, @@ -893,7 +1048,8 @@ public class PaletteControl extends Composite { Display display = getDisplay(); int alpha = (!hasTransparency || !needsContrast) ? IMG_ALPHA : -1; Image swtImage = SwtUtils.convertToSwt(display, cropped, true, alpha); - return swtImage; + Rectangle imageBounds = new Rectangle(0, 0, width, height); + return Pair.of(swtImage, imageBounds); } } } @@ -969,9 +1125,11 @@ public class PaletteControl extends Composite { final static int TOGGLE_ALPHABETICAL = 2; final static int TOGGLE_AUTO_CLOSE = 3; final static int REFRESH = 4; + final static int RESET = 5; ToggleViewOptionAction(String title, int action, boolean checked) { - super(title, action == REFRESH ? IAction.AS_PUSH_BUTTON : IAction.AS_CHECK_BOX); + super(title, (action == REFRESH || action == RESET) ? IAction.AS_PUSH_BUTTON + : IAction.AS_CHECK_BOX); mAction = action; if (checked) { setChecked(checked); @@ -997,6 +1155,13 @@ public class PaletteControl extends Composite { mPreviewIconFactory.refresh(); refreshPalette(); break; + case RESET: + mAlphabetical = false; + mCategories = true; + mAutoClose = true; + mPaletteMode = PaletteMode.SMALL_PREVIEW; + refreshPalette(); + break; } savePaletteMode(); } @@ -1005,41 +1170,63 @@ public class PaletteControl extends Composite { private void addMenu(Control control) { control.addMenuDetectListener(new MenuDetectListener() { public void menuDetected(MenuDetectEvent e) { - MenuManager manager = new MenuManager() { - @Override - public boolean isDynamic() { - return true; - } - }; - boolean previews = previewsAvailable(); - for (PaletteMode mode : PaletteMode.values()) { - if (mode.isPreview() && !previews) { - continue; - } - manager.add(new PaletteModeAction(mode)); - } - if (mPaletteMode.isPreview()) { - manager.add(new Separator()); - manager.add(new ToggleViewOptionAction("Refresh Previews", - ToggleViewOptionAction.REFRESH, - false)); - } - manager.add(new Separator()); - manager.add(new ToggleViewOptionAction("Show Categories", - ToggleViewOptionAction.TOGGLE_CATEGORY, - mCategories)); - manager.add(new ToggleViewOptionAction("Sort Alphabetically", - ToggleViewOptionAction.TOGGLE_ALPHABETICAL, - mAlphabetical)); - manager.add(new Separator()); - manager.add(new ToggleViewOptionAction("Auto Close Previous", - ToggleViewOptionAction.TOGGLE_AUTO_CLOSE, - mAutoClose)); - Menu menu = manager.createContextMenu(PaletteControl.this); - Point point = new Point(e.x, e.y); - menu.setLocation(point.x, point.y); - menu.setVisible(true); + showMenu(e.x, e.y); } }); } + + private void showMenu(int x, int y) { + MenuManager manager = new MenuManager() { + @Override + public boolean isDynamic() { + return true; + } + }; + boolean previews = previewsAvailable(); + for (PaletteMode mode : PaletteMode.values()) { + if (mode.isPreview() && !previews) { + continue; + } + manager.add(new PaletteModeAction(mode)); + } + if (mPaletteMode.isPreview()) { + manager.add(new Separator()); + manager.add(new ToggleViewOptionAction("Refresh Previews", + ToggleViewOptionAction.REFRESH, + false)); + } + manager.add(new Separator()); + manager.add(new ToggleViewOptionAction("Show Categories", + ToggleViewOptionAction.TOGGLE_CATEGORY, + mCategories)); + manager.add(new ToggleViewOptionAction("Sort Alphabetically", + ToggleViewOptionAction.TOGGLE_ALPHABETICAL, + mAlphabetical)); + manager.add(new Separator()); + manager.add(new ToggleViewOptionAction("Auto Close Previous", + ToggleViewOptionAction.TOGGLE_AUTO_CLOSE, + mAutoClose)); + manager.add(new Separator()); + manager.add(new ToggleViewOptionAction("Reset", + ToggleViewOptionAction.RESET, + false)); + + Menu menu = manager.createContextMenu(PaletteControl.this); + menu.setLocation(x, y); + menu.setVisible(true); + } + + private final class ViewFinderListener implements CustomViewFinder.Listener { + private final Composite mParent; + + private ViewFinderListener(Composite parent) { + this.mParent = parent; + } + + public void viewsUpdated(Collection<String> customViews, + Collection<String> thirdPartyViews) { + addCustomItems(mParent); + mParent.layout(true); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java index 3fcae86..812ece4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PlayAnimationMenu.java @@ -15,8 +15,8 @@ */ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; -import static com.android.ide.eclipse.adt.AndroidConstants.WS_SEP; -import static com.android.sdklib.SdkConstants.FD_ANIMATOR; +import static com.android.AndroidConstants.FD_RES_ANIMATOR; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; import static com.android.sdklib.SdkConstants.FD_RESOURCES; import com.android.ide.common.rendering.api.Capability; @@ -124,7 +124,8 @@ public class PlayAnimationMenu extends SubmenuAction { new ActionContributionItem(sub).fill(menu, -1); } } else { - addDisabledMessageItem("Not supported on platform"); + addDisabledMessageItem( + "Not supported for this SDK version; try changing the Render Target"); } } @@ -228,7 +229,7 @@ public class PlayAnimationMenu extends SubmenuAction { LayoutEditor editor = mCanvas.getLayoutEditor(); IWorkbenchWindow workbenchWindow = editor.getEditorSite().getWorkbenchWindow(); IWorkbench workbench = workbenchWindow.getWorkbench(); - String animationDir = FD_RESOURCES + WS_SEP + FD_ANIMATOR; + String animationDir = FD_RESOURCES + WS_SEP + FD_RES_ANIMATOR; Pair<IProject, String> pair = Pair.of(editor.getProject(), animationDir); IStructuredSelection selection = new StructuredSelection(pair); wizard.init(workbench, selection); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java index 1b48c7c..80cc87d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java @@ -17,8 +17,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; -import static com.android.ide.eclipse.adt.AndroidConstants.DOT_PNG; -import static com.android.ide.eclipse.adt.AndroidConstants.DOT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_PNG; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.Capability; @@ -33,6 +33,7 @@ import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; @@ -41,6 +42,7 @@ import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.util.Pair; import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.graphics.RGB; import org.w3c.dom.Attr; @@ -236,7 +238,7 @@ public class PreviewIconFactory { // Important to get these sizes large enough for clients that don't support // RenderMode.FULL_EXPAND such as 1.6 int width = 200; - int height = 2000; + int height = documentElement.getChildNodes().getLength() == 1 ? 400 : 1600; Set<UiElementNode> expandNodes = Collections.<UiElementNode>emptySet(); RenderingMode renderingMode = RenderingMode.FULL_EXPAND; @@ -309,6 +311,13 @@ public class PreviewIconFactory { } } } + } else { + if (session.getResult().getException() != null) { + AdtPlugin.log(session.getResult().getException(), + session.getResult().getErrorMessage()); + } else if (session.getResult().getErrorMessage() != null) { + AdtPlugin.log(IStatus.WARNING, session.getResult().getErrorMessage()); + } } session.dispose(); @@ -329,7 +338,7 @@ public class PreviewIconFactory { RGB background = null; RGB foreground = null; - ResourceResolver resources = mPalette.getEditor().createResolver(); + ResourceResolver resources = mPalette.getEditor().getResourceResolver(); StyleResourceValue theme = resources.getCurrentTheme(); if (theme != null) { background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$ @@ -488,6 +497,19 @@ public class PreviewIconFactory { } private String getFileName(ElementDescriptor descriptor) { + if (descriptor instanceof PaletteMetadataDescriptor) { + PaletteMetadataDescriptor pmd = (PaletteMetadataDescriptor) descriptor; + StringBuilder sb = new StringBuilder(); + String name = pmd.getUiName(); + // Strip out whitespace, parentheses, etc. + for (int i = 0, n = name.length(); i < n; i++) { + char c = name.charAt(i); + if (Character.isLetter(c)) { + sb.append(c); + } + } + return sb.toString() + DOT_PNG; + } return descriptor.getUiName() + DOT_PNG; } @@ -539,7 +561,7 @@ public class PreviewIconFactory { if (themeName.startsWith(themeNamePrefix)) { themeName = themeName.substring(themeNamePrefix.length()); } - String dirName = String.format("palette-preview-r10-%s-%s-%s", cleanup(targetName), + String dirName = String.format("palette-preview-r11c-%s-%s-%s", cleanup(targetName), cleanup(themeName), cleanup(mPalette.getCurrentDevice())); IPath dirPath = pluginState.append(dirName); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java index d33fffc..daf9655 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PropertySheetPage.java @@ -24,7 +24,6 @@ import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.graphics.Region; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; @@ -98,39 +97,18 @@ public class PropertySheetPage extends org.eclipse.ui.views.properties.PropertyS // Fix the selection background. In Eclipse 3.5 and 3.6, the selection color // is white, painted on top of a white or light blue background (table striping), // which is practically unreadable. This is fixed in 3.7M3, but we need a workaround - // for earlier releases. The following code performs custom painting of this region, - // and is based on the snippet "draw a custom gradient selection for tree" (snippet 226) - // from http://www.eclipse.org/swt/snippets/ . + // for earlier releases. This just paints a solid color under the current line in + // the left column. tree.addListener(SWT.EraseItem, new Listener() { public void handleEvent(Event event) { - if ((event.detail & SWT.SELECTED) != 0) { + if ((event.detail & SWT.SELECTED) != 0 && event.index == 0) { GC gc = event.gc; - Rectangle area = tree.getClientArea(); - int columnCount = tree.getColumnCount(); - if (event.index == columnCount - 1 || columnCount == 0) { - int width = area.x + area.width - event.x; - if (width > 0) { - Region region = new Region(); - gc.getClipping(region); - region.add(event.x, event.y, width, event.height); - gc.setClipping(region); - region.dispose(); - } - } - gc.setAdvanced(true); - if (gc.getAdvanced()) { - gc.setAlpha(127); - } Rectangle rect = event.getBounds(); - Color foreground = gc.getForeground(); Color background = gc.getBackground(); Display display = tree.getDisplay(); - gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION)); - gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND)); - gc.fillGradientRectangle(0, rect.y, 500, rect.height, false); - gc.setForeground(foreground); + gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION)); + gc.fillRectangle(rect.x, rect.y, rect.width, rect.height); gc.setBackground(background); - event.detail &= ~SWT.SELECTED; } } }); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java index 983bcf5..01b9314 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java @@ -31,7 +31,7 @@ import java.util.List; /** * Represents one selection in {@link LayoutCanvas}. */ -/* package */ class SelectionItem { +class SelectionItem { /** Current selected view info. Can be null. */ private final CanvasViewInfo mCanvasViewInfo; @@ -139,4 +139,18 @@ import java.util.List; return elements.toArray(new SimpleElement[elements.size()]); } + + /** + * Returns true if this selection item is a layout + * + * @return true if this selection item is a layout + */ + public boolean isLayout() { + UiViewElementNode node = mCanvasViewInfo.getUiViewNode(); + if (node != null) { + return node.getDescriptor().hasChildren(); + } else { + return false; + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java index 1aad0f2..c5a840b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java @@ -16,11 +16,17 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import com.android.ide.common.api.INode; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; import com.android.sdklib.SdkConstants; import org.eclipse.core.runtime.ListenerList; import org.eclipse.gef.ui.parts.TreeViewer; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.Separator; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; @@ -30,8 +36,10 @@ import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TreePath; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IWorkbenchPartSite; import org.w3c.dom.Node; @@ -107,7 +115,7 @@ public class SelectionManager implements ISelectionProvider { * @see #getSelection() {@link #getSelection()} to retrieve a {@link TreeViewer} * compatible {@link ISelection}. */ - /* package */ List<SelectionItem> getSelections() { + List<SelectionItem> getSelections() { return mUnmodifiableSelection; } @@ -135,12 +143,7 @@ public class SelectionManager implements ISelectionProvider { for (SelectionItem cs : mSelections) { CanvasViewInfo vi = cs.getViewInfo(); if (vi != null) { - ArrayList<Object> segments = new ArrayList<Object>(); - while (vi != null) { - segments.add(0, vi); - vi = vi.getParent(); - } - paths.add(new TreePath(segments.toArray())); + paths.add(getTreePath(vi)); } } @@ -148,6 +151,22 @@ public class SelectionManager implements ISelectionProvider { } /** + * Create a {@link TreePath} from the given view info + * + * @param viewInfo the view info to look up a tree path for + * @return a {@link TreePath} for the given view info + */ + public static TreePath getTreePath(CanvasViewInfo viewInfo) { + ArrayList<Object> segments = new ArrayList<Object>(); + while (viewInfo != null) { + segments.add(0, viewInfo); + viewInfo = viewInfo.getParent(); + } + + return new TreePath(segments.toArray()); + } + + /** * Sets the selection. It must be an {@link ITreeSelection} where each segment * of the tree path is a {@link CanvasViewInfo}. A null selection is considered * as an empty selection. @@ -177,6 +196,7 @@ public class SelectionManager implements ISelectionProvider { if (!mSelections.isEmpty()) { mSelections.clear(); mAltSelection = null; + updateActionsFromSelection(); redraw(); } return; @@ -210,7 +230,7 @@ public class SelectionManager implements ISelectionProvider { mSelections.add(createSelection(newVi)); changed = true; } - if (newVi.isInvisibleParent()) { + if (newVi.isInvisible()) { redoLayout = true; } } @@ -230,7 +250,7 @@ public class SelectionManager implements ISelectionProvider { } if (changed) { redraw(); - updateMenuActions(); + updateActionsFromSelection(); } } @@ -240,6 +260,34 @@ public class SelectionManager implements ISelectionProvider { } /** + * The menu has been activated; ensure that the menu click is over the existing + * selection, and if not, update the selection. + * + * @param e the {@link MenuDetectEvent} which triggered the menu + */ + public void menuClick(MenuDetectEvent e) { + LayoutPoint p = ControlPoint.create(mCanvas, e).toLayout(); + + // Right click button is used to display a context menu. + // If there's an existing selection and the click is anywhere in this selection + // and there are no modifiers being used, we don't want to change the selection. + // Otherwise we select the item under the cursor. + + for (SelectionItem cs : mSelections) { + if (cs.isRoot()) { + continue; + } + if (cs.getRect().contains(p.x, p.y)) { + // The cursor is inside the selection. Don't change anything. + return; + } + } + + CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); + selectSingle(vi); + } + + /** * Performs selection for a mouse event. * <p/> * Shift key (or Command on the Mac) is used to toggle in multi-selection. @@ -383,7 +431,7 @@ public class SelectionManager implements ISelectionProvider { if (vi != null) { mSelections.add(createSelection(vi)); - if (vi.isInvisibleParent()) { + if (vi.isInvisible()) { redoLayout = true; } } @@ -425,7 +473,7 @@ public class SelectionManager implements ISelectionProvider { if (viewInfos != null) { for (CanvasViewInfo viewInfo : viewInfos) { mSelections.add(createSelection(viewInfo)); - if (viewInfo.isInvisibleParent()) { + if (viewInfo.isInvisible()) { redoLayout = true; } } @@ -440,11 +488,28 @@ public class SelectionManager implements ISelectionProvider { redraw(); } + public void select(Collection<INode> nodes) { + List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(nodes.size()); + for (INode node : nodes) { + CanvasViewInfo info = mCanvas.getViewHierarchy().findViewInfoFor(node); + if (info != null) { + infos.add(info); + } + } + selectMultiple(infos); + } + /** * Selects the visual element corresponding to the given XML node * @param xmlNode The Node whose element we want to select. */ /* package */ void select(Node xmlNode) { + if (xmlNode == null) { + return; + } else if (xmlNode.getNodeType() == Node.TEXT_NODE) { + xmlNode = xmlNode.getParentNode(); + } + CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoFor(xmlNode); if (vi != null && !vi.isRoot()) { selectSingle(vi); @@ -504,11 +569,72 @@ public class SelectionManager implements ISelectionProvider { mSelections.add(createSelection(vi)); } + fireSelectionChanged(); + redraw(); + } + public void selectNone() { + mSelections.clear(); + mAltSelection = null; fireSelectionChanged(); redraw(); } + /** Selects the parent of the current selection */ + public void selectParent() { + if (mSelections.size() == 1) { + CanvasViewInfo parent = mSelections.get(0).getViewInfo().getParent(); + if (parent != null) { + selectSingle(parent); + } + } + } + + /** Finds all widgets in the layout that have the same type as the primary */ + public void selectSameType() { + // Find all + if (mSelections.size() == 1) { + CanvasViewInfo viewInfo = mSelections.get(0).getViewInfo(); + ElementDescriptor descriptor = viewInfo.getUiViewNode().getDescriptor(); + mSelections.clear(); + mAltSelection = null; + addSameType(mCanvas.getViewHierarchy().getRoot(), descriptor); + fireSelectionChanged(); + redraw(); + } + } + + /** Helper for {@link #selectSameType} */ + private void addSameType(CanvasViewInfo root, ElementDescriptor descriptor) { + if (root.getUiViewNode().getDescriptor() == descriptor) { + mSelections.add(createSelection(root)); + } + + for (CanvasViewInfo child : root.getChildren()) { + addSameType(child, descriptor); + } + } + + /** Selects the siblings of the primary */ + public void selectSiblings() { + // Find all + if (mSelections.size() == 1) { + CanvasViewInfo vi = mSelections.get(0).getViewInfo(); + mSelections.clear(); + mAltSelection = null; + CanvasViewInfo parent = vi.getParent(); + if (parent == null) { + selectNone(); + } else { + for (CanvasViewInfo child : parent.getChildren()) { + mSelections.add(createSelection(child)); + } + fireSelectionChanged(); + redraw(); + } + } + } + /** * Returns true if and only if there is currently more than one selected * item. @@ -601,13 +727,25 @@ public class SelectionManager implements ISelectionProvider { } }); + updateActionsFromSelection(); + } finally { + mInsideUpdateSelection = false; + } + } + + /** + * Updates menu actions and the layout action bar after a selection change - these are + * actions that depend on the selection + */ + private void updateActionsFromSelection() { + LayoutEditor editor = mCanvas.getLayoutEditor(); + if (editor != null) { // Update menu actions that depend on the selection updateMenuActions(); // Update the layout actions bar - mCanvas.getLayoutEditor().getGraphicalEditor().getLayoutActionBar().updateSelection(); - } finally { - mInsideUpdateSelection = false; + LayoutActionBar layoutActionBar = editor.getGraphicalEditor().getLayoutActionBar(); + layoutActionBar.updateSelection(); } } @@ -685,21 +823,28 @@ public class SelectionManager implements ISelectionProvider { * Update the outline selection to select the given nodes, asynchronously. * @param nodes The nodes to be selected */ - public void updateOutlineSelection(final List<INode> nodes) { + public void setOutlineSelection(final List<INode> nodes) { Display.getDefault().asyncExec(new Runnable() { public void run() { selectDropped(nodes); - OutlinePage outlinePage = mCanvas.getOutlinePage(); - IWorkbenchPartSite site = outlinePage.getEditor().getSite(); - ISelectionProvider selectionProvider = site.getSelectionProvider(); - ISelection selection = selectionProvider.getSelection(); - if (selection != null) { - outlinePage.setSelection(selection); - } + syncOutlineSelection(); } }); } + /** + * Syncs the current selection to the outline, synchronously. + */ + public void syncOutlineSelection() { + OutlinePage outlinePage = mCanvas.getOutlinePage(); + IWorkbenchPartSite site = outlinePage.getEditor().getSite(); + ISelectionProvider selectionProvider = site.getSelectionProvider(); + ISelection selection = selectionProvider.getSelection(); + if (selection != null) { + outlinePage.setSelection(selection); + } + } + private void updateMenuActions() { boolean hasSelection = !mSelections.isEmpty(); mCanvas.updateMenuActionState(hasSelection); @@ -713,4 +858,111 @@ public class SelectionManager implements ISelectionProvider { return new SelectionItem(vi, mCanvas.getRulesEngine(), mCanvas.getNodeFactory()); } + + /** + * Returns true if there is nothing selected + * + * @return true if there is nothing selected + */ + public boolean isEmpty() { + return mSelections.size() == 0; + } + + /** + * "Select" context menu which lists various menu options related to selection: + * <ul> + * <li> Select All + * <li> Select Parent + * <li> Select None + * <li> Select Siblings + * <li> Select Same Type + * </ul> + * etc. + */ + public static class SelectionMenu extends SubmenuAction { + private final GraphicalEditorPart mEditor; + + public SelectionMenu(GraphicalEditorPart editor) { + super("Select"); + mEditor = editor; + } + + @Override + public String getId() { + return "-selectionmenu"; //$NON-NLS-1$ + } + + @Override + protected void addMenuItems(Menu menu) { + LayoutCanvas canvas = mEditor.getCanvasControl(); + SelectionManager selectionManager = canvas.getSelectionManager(); + List<SelectionItem> selections = selectionManager.getSelections(); + boolean selectedOne = selections.size() == 1; + boolean notRoot = selectedOne && !selections.get(0).isRoot(); + boolean haveSelection = selections.size() > 0; + + Action a; + a = selectionManager.new SelectAction("Select Parent", SELECT_PARENT); + new ActionContributionItem(a).fill(menu, -1); + a.setEnabled(notRoot); + a.setAccelerator(SWT.ESC); + + a = selectionManager.new SelectAction("Select Siblings", SELECT_SIBLINGS); + new ActionContributionItem(a).fill(menu, -1); + a.setEnabled(notRoot); + + a = selectionManager.new SelectAction("Select Same Type", SELECT_SAME_TYPE); + new ActionContributionItem(a).fill(menu, -1); + a.setEnabled(selectedOne); + + new Separator().fill(menu, -1); + + // Special case for Select All: Use global action + a = canvas.getSelectAllAction(); + new ActionContributionItem(a).fill(menu, -1); + a.setEnabled(true); + + a = selectionManager.new SelectAction("Select None", SELECT_NONE); + new ActionContributionItem(a).fill(menu, -1); + a.setEnabled(haveSelection); + } + } + + private static final int SELECT_PARENT = 1; + private static final int SELECT_SIBLINGS = 2; + private static final int SELECT_SAME_TYPE = 3; + private static final int SELECT_NONE = 4; // SELECT_ALL is handled separately + + private class SelectAction extends Action { + private final int mType; + + public SelectAction(String title, int type) { + super(title, IAction.AS_PUSH_BUTTON); + mType = type; + } + + @Override + public void run() { + switch (mType) { + case SELECT_NONE: + selectNone(); + break; + case SELECT_PARENT: + selectParent(); + break; + case SELECT_SAME_TYPE: + selectSameType(); + break; + case SELECT_SIBLINGS: + selectSiblings(); + break; + } + + List<INode> nodes = new ArrayList<INode>(); + for (SelectionItem item : getSelections()) { + nodes.add(item.getNode()); + } + setOutlineSelection(nodes); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java index 2748297..2a926a7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtDrawingStyle.java @@ -44,7 +44,12 @@ public enum SwtDrawingStyle { /** * The style definition corresponding to {@link DrawingStyle#HOVER} */ - HOVER(null, 0, new RGB(0xFF, 0xFF, 0xFF), 64, 1, SWT.LINE_DOT), + HOVER(null, 0, new RGB(0xFF, 0xFF, 0xFF), 40, 1, SWT.LINE_DOT), + + /** + * The style definition corresponding to {@link DrawingStyle#HOVER} + */ + HOVER_SELECTION(null, 0, new RGB(0xFF, 0xFF, 0xFF), 10, 1, SWT.LINE_DOT), /** * The style definition corresponding to {@link DrawingStyle#ANCHOR} @@ -59,7 +64,7 @@ public enum SwtDrawingStyle { /** * The style definition corresponding to {@link DrawingStyle#DROP_RECIPIENT} */ - DROP_RECIPIENT(new RGB(0xFF, 0x99, 0x00), 255, new RGB(0xFF, 0x99, 0x00), 160, 1, + DROP_RECIPIENT(new RGB(0xFF, 0x99, 0x00), 255, new RGB(0xFF, 0x99, 0x00), 160, 2, SWT.LINE_SOLID), /** @@ -199,6 +204,8 @@ public enum SwtDrawingStyle { return GUIDELINE; case HOVER: return HOVER; + case HOVER_SELECTION: + return HOVER_SELECTION; case ANCHOR: return ANCHOR; case OUTLINE: diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java index eac2228..10625bb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java @@ -44,6 +44,8 @@ import java.util.Set; * operations on this set. */ public class ViewHierarchy { + private static final boolean DUMP_INFO = false; + private LayoutCanvas mCanvas; /** @@ -80,7 +82,7 @@ public class ViewHierarchy { private boolean mIsResultValid; /** - * A list of invisible parents (see {@link CanvasViewInfo#isInvisibleParent()} for + * A list of invisible parents (see {@link CanvasViewInfo#isInvisible()} for * details) in the current view hierarchy. */ private final List<CanvasViewInfo> mInvisibleParents = new ArrayList<CanvasViewInfo>(); @@ -134,7 +136,8 @@ public class ViewHierarchy { * {@link LayoutCanvas#showInvisibleViews}) where individual invisible * nodes are padded during certain interactions. */ - /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes) { + /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes, + boolean layoutlib5) { // replace the previous scene, so the previous scene must be disposed. if (mSession != null) { mSession.dispose(); @@ -155,7 +158,7 @@ public class ViewHierarchy { // via drag & drop, etc. if (hasMergeRoot()) { ViewInfo mergeRoot = createMergeInfo(session); - infos = CanvasViewInfo.create(mergeRoot); + infos = CanvasViewInfo.create(mergeRoot, layoutlib5); } else { infos = null; } @@ -163,11 +166,15 @@ public class ViewHierarchy { if (rootList.size() > 1 && hasMergeRoot()) { ViewInfo mergeRoot = createMergeInfo(session); mergeRoot.setChildren(rootList); - infos = CanvasViewInfo.create(mergeRoot); + infos = CanvasViewInfo.create(mergeRoot, layoutlib5); } else { ViewInfo root = rootList.get(0); + if (root != null) { - infos = CanvasViewInfo.create(root); + infos = CanvasViewInfo.create(root, layoutlib5); + if (DUMP_INFO) { + dump(root, 0); + } } else { infos = null; } @@ -176,12 +183,26 @@ public class ViewHierarchy { if (infos != null) { mLastValidViewInfoRoot = infos.getFirst(); mIncludedBounds = infos.getSecond(); + + if (mLastValidViewInfoRoot.getUiViewNode() == null && + mLastValidViewInfoRoot.getChildren().isEmpty()) { + GraphicalEditorPart editor = mCanvas.getLayoutEditor().getGraphicalEditor(); + if (editor.getIncludedWithin() != null) { + // Somehow, this view was supposed to be rendered within another + // view, yet this view was rendered as part of the other view. + // In that case, abort attempting to show included in; clear the + // include context and trigger a standalone re-render. + editor.showIn(null); + return; + } + } + } else { mLastValidViewInfoRoot = null; mIncludedBounds = null; } - updateNodeProxies(mLastValidViewInfoRoot, null); + updateNodeProxies(mLastValidViewInfoRoot); // Update the data structures related to tracking invisible and exploded nodes. // We need to find the {@link CanvasViewInfo} objects that correspond to @@ -238,7 +259,7 @@ public class ViewHierarchy { * This is a recursive call that updates the whole hierarchy starting at the given * view info. */ - private void updateNodeProxies(CanvasViewInfo vi, UiViewElementNode parentKey) { + private void updateNodeProxies(CanvasViewInfo vi) { if (vi == null) { return; } @@ -250,7 +271,7 @@ public class ViewHierarchy { } for (CanvasViewInfo child : vi.getChildren()) { - updateNodeProxies(child, key); + updateNodeProxies(child); } } @@ -274,7 +295,7 @@ public class ViewHierarchy { return; } - if (vi.isInvisibleParent()) { + if (vi.isInvisible()) { mInvisibleParents.add(vi); } else if (invisibleNodes != null) { UiViewElementNode key = vi.getUiViewNode(); @@ -670,4 +691,44 @@ public class ViewHierarchy { return mIncludedBounds; } + /** + * Dumps a {@link ViewInfo} hierarchy to stdout + * + * @param info the {@link ViewInfo} object to dump + * @param depth the depth to indent it to + */ + public static void dump(ViewInfo info, int depth) { + if (DUMP_INFO) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < depth; i++) { + sb.append(" "); //$NON-NLS-1$ + } + sb.append(info.getClassName()); + sb.append(" ["); //$NON-NLS-1$ + sb.append(info.getLeft()); + sb.append(","); //$NON-NLS-1$ + sb.append(info.getTop()); + sb.append(","); //$NON-NLS-1$ + sb.append(info.getRight()); + sb.append(","); //$NON-NLS-1$ + sb.append(info.getBottom()); + sb.append("]"); //$NON-NLS-1$ + Object cookie = info.getCookie(); + if (cookie instanceof UiViewElementNode) { + sb.append(" "); //$NON-NLS-1$ + UiViewElementNode node = (UiViewElementNode) cookie; + sb.append("<"); //$NON-NLS-1$ + sb.append(node.getDescriptor().getXmlName()); + sb.append(">"); //$NON-NLS-1$ + } else if (cookie != null) { + sb.append(" " + cookie); //$NON-NLS-1$ + } + + System.out.println(sb.toString()); + + for (ViewInfo child : info.getChildren()) { + dump(child, depth + 1); + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java index 59c6e15..bd8e75e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java @@ -203,6 +203,12 @@ public class NodeProxy implements INode { return insertOrAppend(viewFqcn, index); } + public void removeChild(INode node) { + checkEditOK(); + + ((NodeProxy) node).mNode.deleteXmlNode(); + } + private INode insertOrAppend(String viewFqcn, int index) { checkEditOK(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java new file mode 100644 index 0000000..3380a38 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/PaletteMetadataDescriptor.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gre; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; + +import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleAttribute; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleElement; + +import org.eclipse.swt.graphics.Image; +import org.w3c.dom.Element; + +/** + * Special version of {@link ViewElementDescriptor} which is initialized by the palette + * with specific metadata for how to instantiate particular variations of an existing + * {@link ViewElementDescriptor} with initial values. + */ +public class PaletteMetadataDescriptor extends ViewElementDescriptor { + private String mInitString; + private String mIconName; + + public PaletteMetadataDescriptor(ViewElementDescriptor descriptor, String displayName, + String initString, String iconName) { + super(descriptor.getXmlName(), + displayName, + descriptor.getFullClassName(), + descriptor.getTooltip(), + descriptor.getSdkUrl(), + descriptor.getAttributes(), + descriptor.getLayoutAttributes(), + descriptor.getChildren(), descriptor.getMandatory() == Mandatory.MANDATORY); + mInitString = initString; + mIconName = iconName; + } + + /** + * Returns a String which contains a comma separated list of name=value tokens, + * where the name can start with "android:" to indicate a property in the android namespace, + * or no prefix for plain attributes. + * + * @return the initialization string, which can be empty but never null + */ + public String getInitializedAttributes() { + return mInitString != null ? mInitString : ""; //$NON-NLS-1$ + } + + @Override + public Image getGenericIcon() { + if (mIconName != null) { + IconFactory factory = IconFactory.getInstance(); + Image icon = factory.getIcon(mIconName); + if (icon != null) { + return icon; + } + } + + return super.getGenericIcon(); + } + + /** + * Initializes a new {@link SimpleElement} with the palette initialization + * configuration + * + * @param element the new element to initialize + */ + public void initializeNew(SimpleElement element) { + initializeNew(element, null); + } + + /** + * Initializes a new {@link Element} with the palette initialization configuration + * + * @param element the new element to initialize + */ + public void initializeNew(Element element) { + initializeNew(null, element); + } + + private void initializeNew(SimpleElement simpleElement, Element domElement) { + String initializedAttributes = mInitString; + if (initializedAttributes != null && initializedAttributes.length() > 0) { + for (String s : initializedAttributes.split(",")) { //$NON-NLS-1$ + String[] nameValue = s.split("="); //$NON-NLS-1$ + String name = nameValue[0]; + String value = nameValue[1]; + String nameSpace = ""; //$NON-NLS-1$ + if (name.startsWith(ANDROID_NS_NAME_PREFIX)) { + name = name.substring(ANDROID_NS_NAME_PREFIX.length()); + nameSpace = ANDROID_URI; + } + + if (simpleElement != null) { + SimpleAttribute attr = new SimpleAttribute(nameSpace, name, value); + simpleElement.addAttribute(attr); + } + + if (domElement != null) { + domElement.setAttributeNS(nameSpace, name, value); + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java index cde4e28..6838432 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_MERGE; import com.android.ide.common.api.DropFeedback; @@ -30,14 +31,17 @@ import com.android.ide.common.api.InsertType; import com.android.ide.common.api.MenuAction; import com.android.ide.common.api.Point; import com.android.ide.common.layout.ViewRule; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SimpleElement; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; +import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; @@ -62,6 +66,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -600,7 +605,7 @@ public class RulesEngine { // Deal with unknown descriptors; these lack the full qualified path and // elements in the layout without a package are taken to be in the // android.widget package. - fqcn = "android.widget." + fqcn; //$NON-NLS-1$ + fqcn = ANDROID_WIDGET_PREFIX + fqcn; } // Try to find a rule matching the "real" FQCN. If we find it, we're done. @@ -851,7 +856,7 @@ public class RulesEngine { IProject project = editor.getProject(); if (project != null) { // get the resource repository for this project and the system resources. - IResourceRepository projectRepository = + ResourceRepository projectRepository = ResourceManager.getInstance().getProjectResources(project); Shell shell = AdtPlugin.getDisplay().getActiveShell(); if (shell == null) { @@ -873,12 +878,17 @@ public class RulesEngine { } public String displayResourceInput(String resourceTypeName, String currentValue) { + return displayResourceInput(resourceTypeName, currentValue, null); + } + + private String displayResourceInput(String resourceTypeName, String currentValue, + IInputValidator validator) { AndroidXmlEditor editor = mEditor.getLayoutEditor(); IProject project = editor.getProject(); - ResourceType type = ResourceType.valueOf(resourceTypeName.toUpperCase()); + ResourceType type = ResourceType.getEnum(resourceTypeName); if (project != null) { // get the resource repository for this project and the system resources. - IResourceRepository projectRepository = ResourceManager.getInstance() + ResourceRepository projectRepository = ResourceManager.getInstance() .getProjectResources(project); Shell shell = AdtPlugin.getDisplay().getActiveShell(); if (shell == null) { @@ -886,15 +896,24 @@ public class RulesEngine { } AndroidTargetData data = editor.getTargetData(); - IResourceRepository systemRepository = data.getSystemResources(); + ResourceRepository systemRepository = data.getFrameworkResources(); // open a resource chooser dialog for specified resource type. ResourceChooser dlg = new ResourceChooser(project, type, projectRepository, systemRepository, shell); + if (validator != null) { + // Ensure wide enough to accommodate validator error message + dlg.setSize(70, 10); + dlg.setInputValidator(validator); + } + dlg.setCurrentResource(currentValue); - if (dlg.open() == Window.OK) { + int result = dlg.open(); + if (result == ResourceChooser.CLEAR_RETURN_CODE) { + return ""; //$NON-NLS-1$ + } else if (result == Window.OK) { return dlg.getCurrentResource(); } } @@ -921,5 +940,25 @@ public class RulesEngine { return null; } + + public String displayIncludeSourceInput() { + AndroidXmlEditor editor = mEditor.getLayoutEditor(); + IInputValidator validator = CyclicDependencyValidator.create(editor.getInputFile()); + return displayResourceInput(ResourceType.LAYOUT.getName(), null, validator); + } + + public void select(final Collection<INode> nodes) { + LayoutCanvas layoutCanvas = mEditor.getCanvasControl(); + final SelectionManager selectionManager = layoutCanvas.getSelectionManager(); + selectionManager.select(nodes); + // ALSO run an async select since immediately after nodes are created they + // may not be selectable. We can't ONLY run an async exec since + // code may depend on operating on the selection. + layoutCanvas.getDisplay().asyncExec(new Runnable() { + public void run() { + selectionManager.select(nodes); + } + }); + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java index 589ad2d..cf6acba 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java @@ -16,14 +16,15 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; -import static com.android.ide.common.api.IViewMetadata.FillPreference.NONE; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; +import com.android.annotations.VisibleForTesting; import com.android.ide.common.api.IViewMetadata.FillPreference; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; @@ -38,13 +39,14 @@ import org.xml.sax.InputSource; import java.io.BufferedInputStream; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; +import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -147,9 +149,7 @@ public class ViewMetadataRepository { // Therefore, we instead use the convention that the id is the fully qualified // class name, with .'s replaced with _'s. - // Special case: for tab host we aren't allowed to mess with the id - String id = element.getAttributeNS(ANDROID_URI, ATTR_ID); if ("@android:id/tabhost".equals(id)) { @@ -218,28 +218,11 @@ public class ViewMetadataRepository { Node childNode = children.item(j); if (childNode.getNodeType() == Node.ELEMENT_NODE) { Element child = (Element) childNode; - String fqcn = child.getAttribute("class"); //$NON-NLS-1$ - String fill = child.getAttribute("fill"); //$NON-NLS-1$ - FillPreference fillPreference = null; - if (fill.length() > 0) { - fillPreference = fillTypes.get(fill); - } - if (fillPreference == null) { - fillPreference = NONE; - } - String skip = child.getAttribute("skip"); //$NON-NLS-1$ - RenderMode mode = RenderMode.NORMAL; - String render = child.getAttribute("render"); //$NON-NLS-1$ - if (render.length() > 0) { - mode = RenderMode.get(render); - } - ViewData view = new ViewData(category, fqcn, fillPreference, - skip.length() == 0 ? false : Boolean.valueOf(skip), - mode); + ViewData view = createViewData(fillTypes, child, + null, FillPreference.NONE, RenderMode.NORMAL); category.addView(view); } } - mCategories.add(category); } } @@ -252,6 +235,62 @@ public class ViewMetadataRepository { return mCategories; } + private ViewData createViewData(Map<String, FillPreference> fillTypes, + Element child, String defaultFqcn, FillPreference defaultFill, + RenderMode defaultRender) { + String fqcn = child.getAttribute("class"); //$NON-NLS-1$ + if (fqcn.length() == 0) { + fqcn = defaultFqcn; + } + String fill = child.getAttribute("fill"); //$NON-NLS-1$ + FillPreference fillPreference = null; + if (fill.length() > 0) { + fillPreference = fillTypes.get(fill); + } + if (fillPreference == null) { + fillPreference = defaultFill; + } + String skip = child.getAttribute("skip"); //$NON-NLS-1$ + RenderMode renderMode = defaultRender; + String render = child.getAttribute("render"); //$NON-NLS-1$ + if (render.length() > 0) { + renderMode = RenderMode.get(render); + } + String displayName = child.getAttribute("name"); //$NON-NLS-1$ + if (displayName.length() == 0) { + displayName = null; + } + String relatedTo = child.getAttribute("relatedTo"); //$NON-NLS-1$ + ViewData view = new ViewData(fqcn, displayName, fillPreference, + skip.length() == 0 ? false : Boolean.valueOf(skip), + renderMode, relatedTo); + + String init = child.getAttribute("init"); //$NON-NLS-1$ + String icon = child.getAttribute("icon"); //$NON-NLS-1$ + + view.setInitString(init); + if (icon.length() > 0) { + view.setIconName(icon); + } + + // Nested variations? + if (child.hasChildNodes()) { + // Palette variations + NodeList childNodes = child.getChildNodes(); + for (int k = 0, kl = childNodes.getLength(); k < kl; k++) { + Node variationNode = childNodes.item(k); + if (variationNode.getNodeType() == Node.ELEMENT_NODE) { + Element variation = (Element) variationNode; + ViewData variationView = createViewData(fillTypes, variation, + fqcn, fillPreference, renderMode); + view.addVariation(variationView); + } + } + } + + return view; + } + /** * Computes the palette entries for the given {@link AndroidTargetData}, looking up the * available node descriptors, categorizing and sorting them. @@ -267,108 +306,97 @@ public class ViewMetadataRepository { List<Pair<String, List<ViewElementDescriptor>>> result = new ArrayList<Pair<String, List<ViewElementDescriptor>>>(); - final Map<String, ViewData> viewMap = getClassToView(); - Map<CategoryData, List<ViewElementDescriptor>> categories = - new TreeMap<CategoryData, List<ViewElementDescriptor>>(); - - // Locate the "Other" category - CategoryData other = null; - for (CategoryData category : getCategories()) { - if (category.getViewCount() == 0) { - other = category; - break; - } - } - List<List<ViewElementDescriptor>> lists = new ArrayList<List<ViewElementDescriptor>>(2); LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors(); lists.add(layoutDescriptors.getViewDescriptors()); lists.add(layoutDescriptors.getLayoutDescriptors()); + // First record map of FQCN to ViewElementDescriptor such that we can quickly + // determine if a particular palette entry is available + Map<String, ViewElementDescriptor> fqcnToDescriptor = + new HashMap<String, ViewElementDescriptor>(); for (List<ViewElementDescriptor> list : lists) { for (ViewElementDescriptor view : list) { - ViewData viewData = getClassToView().get(view.getFullClassName()); - CategoryData category = other; - if (viewData != null) { - if (viewData.getSkip()) { - continue; - } - category = viewData.getCategory(); + String fqcn = view.getFullClassName(); + if (fqcn == null) { + // <view> and <merge> tags etc + fqcn = view.getUiName(); } + fqcnToDescriptor.put(fqcn, view); + } + } - List<ViewElementDescriptor> viewList = categories.get(category); - if (viewList == null) { - viewList = new ArrayList<ViewElementDescriptor>(); - categories.put(category, viewList); - } - viewList.add(view); + Set<ViewElementDescriptor> remaining = new HashSet<ViewElementDescriptor>( + layoutDescriptors.getViewDescriptors().size() + + layoutDescriptors.getLayoutDescriptors().size()); + remaining.addAll(layoutDescriptors.getViewDescriptors()); + remaining.addAll(layoutDescriptors.getLayoutDescriptors()); + // Now iterate in palette metadata order over the items in the palette and include + // any that also appear as a descriptor + List<ViewElementDescriptor> categoryItems = new ArrayList<ViewElementDescriptor>(); + for (CategoryData category : getCategories()) { + if (createCategories) { + categoryItems = new ArrayList<ViewElementDescriptor>(); + } + for (ViewData view : category) { + String fqcn = view.getFcqn(); + ViewElementDescriptor descriptor = fqcnToDescriptor.get(fqcn); + if (descriptor != null) { + if (view.getDisplayName() != null || view.getInitString().length() > 0) { + categoryItems.add(new PaletteMetadataDescriptor(descriptor, + view.getDisplayName(), view.getInitString(), view.getIconName())); + } else { + categoryItems.add(descriptor); + } + remaining.remove(descriptor); + + if (view.hasVariations()) { + for (ViewData variation : view.getVariations()) { + String init = variation.getInitString(); + String icon = variation.getIconName(); + ViewElementDescriptor desc = new PaletteMetadataDescriptor(descriptor, + variation.getDisplayName(), init, icon); + categoryItems.add(desc); + } + } + } } - } - if (!createCategories) { - // Squash all categories into a single one, "Views" - Map<CategoryData, List<ViewElementDescriptor>> singleCategory = - new HashMap<CategoryData, List<ViewElementDescriptor>>(); - List<ViewElementDescriptor> items = new ArrayList<ViewElementDescriptor>(100); - for (Map.Entry<CategoryData, List<ViewElementDescriptor>> entry : categories.entrySet()) { - items.addAll(entry.getValue()); + if (createCategories && categoryItems.size() > 0) { + if (alphabetical) { + Collections.sort(categoryItems); + } + result.add(Pair.of(category.getName(), categoryItems)); } - singleCategory.put(new CategoryData("Views"), items); - categories = singleCategory; } - for (Map.Entry<CategoryData, List<ViewElementDescriptor>> entry : categories.entrySet()) { - String name = entry.getKey().getName(); - List<ViewElementDescriptor> items = entry.getValue(); - if (items == null) { - continue; // empty category + if (remaining.size() > 0) { + List<ViewElementDescriptor> otherItems = new ArrayList<ViewElementDescriptor>(remaining); + // Always sorted, we don't have a natural order for these unknowns + Collections.sort(otherItems); + if (createCategories) { + result.add(Pair.of("Other", otherItems)); + } else { + categoryItems.addAll(otherItems); } + } - // Natural sort of the descriptors + if (!createCategories) { if (alphabetical) { - Collections.sort(items); - } else { - Collections.sort(items, new Comparator<ViewElementDescriptor>() { - public int compare(ViewElementDescriptor v1, ViewElementDescriptor v2) { - String fqcn1 = v1.getFullClassName(); - String fqcn2 = v2.getFullClassName(); - if (fqcn1 == null) { - // <view> and <merge> tags etc - fqcn1 = v1.getUiName(); - } - if (fqcn2 == null) { - fqcn2 = v2.getUiName(); - } - ViewData d1 = viewMap.get(fqcn1); - ViewData d2 = viewMap.get(fqcn2); - - // Use natural sorting order of the view data - // Sort unknown views to the end (and alphabetically among themselves) - if (d1 != null) { - if (d2 != null) { - return d1.getOrdinal() - d2.getOrdinal(); - } else { - return 1; - } - } else { - if (d2 == null) { - return v1.getUiName().compareTo(v2.getUiName()); - } else { - return -1; - } - } - } - }); + Collections.sort(categoryItems); } - - - result.add(Pair.of(name, items)); + result.add(Pair.of("Views", categoryItems)); } return result; } + @VisibleForTesting + Collection<String> getAllFqcns() { + return getClassToView().keySet(); + } + /** * Metadata holder for a particular category - contains the name of the category, its * ordinal (for natural/logical sorting order) and views contained in the category @@ -396,10 +424,6 @@ public class ViewMetadataRepository { return mName; } - public int getViewCount() { - return mViews.size(); - } - // Implements Iterable<ViewData> such that we can use for-each on the category to // enumerate its views public Iterator<ViewData> iterator() { @@ -418,29 +442,37 @@ public class ViewMetadataRepository { private final String mFqcn; /** Fill preference of the view */ private final FillPreference mFillPreference; - /** The category that the view belongs to */ - private final CategoryData mCategory; /** Skip this item in the palette? */ private final boolean mSkip; /** Must this item be rendered alone? skipped? etc */ private final RenderMode mRenderMode; + /** Related views */ + private final String mRelatedTo; /** The relative rank of the view for natural ordering */ private final int mOrdinal = sNextOrdinal++; + /** List of optional variations */ + private List<ViewData> mVariations; + /** Display name. Can be null. */ + private String mDisplayName; + /** + * Optional initialization string - a comma separate set of name/value pairs to + * initialize the element with + */ + private String mInitString; + /** The name of an icon (known to the {@link IconFactory} to show for this view */ + private String mIconName; /** Constructs a new view data for the given class */ - private ViewData(CategoryData category, String fqcn, - FillPreference fillPreference, boolean skip, RenderMode renderMode) { + private ViewData(String fqcn, String displayName, + FillPreference fillPreference, boolean skip, RenderMode renderMode, + String relatedTo) { super(); - mCategory = category; mFqcn = fqcn; + mDisplayName = displayName; mFillPreference = fillPreference; mSkip = skip; mRenderMode = renderMode; - } - - /** Returns the category for views of this type */ - private CategoryData getCategory() { - return mCategory; + mRelatedTo = relatedTo; } /** Returns the {@link FillPreference} for views of this type */ @@ -453,9 +485,8 @@ public class ViewMetadataRepository { return mFqcn; } - /** Relative rank of this view type */ - private int getOrdinal() { - return mOrdinal; + private String getDisplayName() { + return mDisplayName; } // Implements Comparable<ViewData> such that views can be sorted naturally @@ -470,6 +501,64 @@ public class ViewMetadataRepository { public boolean getSkip() { return mSkip; } + + public List<String> getRelatedTo() { + if (mRelatedTo == null || mRelatedTo.length() == 0) { + return Collections.emptyList(); + } else { + String[] basenames = mRelatedTo.split(","); //$NON-NLS-1$ + List<String> result = new ArrayList<String>(); + ViewMetadataRepository repository = ViewMetadataRepository.get(); + Map<String, ViewData> classToView = repository.getClassToView(); + + List<String> fqns = new ArrayList<String>(classToView.keySet()); + for (String basename : basenames) { + boolean found = false; + for (String fqcn : fqns) { + String suffix = '.' + basename; + if (fqcn.endsWith(suffix)) { + result.add(fqcn); + found = true; + break; + } + } + assert found : basename; + } + + return result; + } + } + + void addVariation(ViewData variation) { + if (mVariations == null) { + mVariations = new ArrayList<ViewData>(4); + } + mVariations.add(variation); + } + + List<ViewData> getVariations() { + return mVariations; + } + + boolean hasVariations() { + return mVariations != null && mVariations.size() > 0; + } + + private void setInitString(String initString) { + this.mInitString = initString; + } + + private String getInitString() { + return mInitString; + } + + private void setIconName(String iconName) { + this.mIconName = iconName; + } + + private String getIconName() { + return mIconName; + } } /** @@ -501,7 +590,7 @@ public class ViewMetadataRepository { return view.getRenderMode(); } - return RenderMode.ALONE; + return RenderMode.NORMAL; } /** @@ -520,6 +609,23 @@ public class ViewMetadataRepository { return false; } + /** + * Returns a set of fully qualified names for views that are closely related to the + * given view + * + * @param fqcn the fully qualified class name + * @return a list, never null but possibly empty, of views that are related to the + * view of the given type + */ + public List<String> getRelatedTo(String fqcn) { + ViewData view = getClassToView().get(fqcn); + if (view != null) { + return view.getRelatedTo(); + } + + return Collections.emptyList(); + } + /** Render mode for palette preview */ public enum RenderMode { /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml index 627c2e9..442b76c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- - Palette Metadata + <!-- + Palette Metadata - This document provides additional designtime metadata for various Android views, - such as logical palette categories (as well as a natural ordering of the views within - their categories, fill-preferences (how a view will sets its width and height attributes - when dropped into other views), and so on. - --> + This document provides additional designtime metadata for various Android views, such as + logical palette categories (as well as a natural ordering of the views within their + categories, fill-preferences (how a view will sets its width and height attributes when + dropped into other views), and so on. + --> <!DOCTYPE metadata [ <!--- The metadata consists of a series of category definitions --> <!ELEMENT metadata (category)*> @@ -15,10 +15,13 @@ <!ATTLIST category name CDATA #IMPLIED> <!--- Each view is identified by its full class name and has various other attributes such as a fill preference --> -<!ELEMENT view EMPTY> +<!ELEMENT view (view)*> <!ATTLIST view - class CDATA #REQUIRED - previewXml CDATA #IMPLIED + class CDATA #IMPLIED + name CDATA #IMPLIED + init CDATA #IMPLIED + icon CDATA #IMPLIED + relatedTo CDATA #IMPLIED skip (true|false) "false" render (alone|skip|normal) "normal" fill ( none|both|width|height|opposite| @@ -26,81 +29,316 @@ > ]> <metadata> - <category name="Form Widgets"> - <view class="android.widget.TextView" /> - <view class="android.widget.Button" /> - <view class="android.widget.CheckBox" /> - <view class="android.widget.ToggleButton" /> - <view class="android.widget.RadioButton" /> - <view class="android.widget.CheckedTextView" /> - <view class="android.widget.Spinner" fill="width_in_vertical" /> - <view class="android.widget.EditText" fill="width_in_vertical" /> - <view class="android.widget.AutoCompleteTextView" fill="width_in_vertical" /> - <view class="android.widget.MultiAutoCompleteTextView" fill="width_in_vertical" /> - <view class="android.widget.ProgressBar" /> - <view class="android.widget.QuickContactBadge" /> - <view class="android.widget.RadioGroup" /> - <view class="android.widget.RatingBar" /> - <view class="android.widget.SeekBar" fill="width_in_vertical" /> + <category + name="Form Widgets"> + <view + class="android.widget.TextView" + name="TextView" + init="" + relatedTo="EditText,AutoCompleteTextView,MultiAutoCompleteTextView"> + <view + name="Large Text" + init="android:textAppearance=?android:attr/textAppearanceLarge" /> + <view + name="Medium Text" + init="android:textAppearance=?android:attr/textAppearanceMedium" /> + <view + name="Small Text" + init="android:textAppearance=?android:attr/textAppearanceSmall" /> + </view> + <view + class="android.widget.Button" + relatedTo="ImageButton" /> + <view + class="android.widget.ToggleButton" + relatedTo="CheckBox" /> + <view + class="android.widget.CheckBox" + relatedTo="RadioButton,ToggleButton,CheckedTextView" /> + <view + class="android.widget.RadioButton" + relatedTo="CheckBox,ToggleButton" /> + <view + class="android.widget.CheckedTextView" + relatedTo="TextView,CheckBox" /> + <view + class="android.widget.Spinner" + relatedTo="EditText" + fill="width_in_vertical" /> + <view + class="android.widget.ProgressBar" + relatedTo="SeekBar" + name="ProgressBar (Large)" + init="style=?android:attr/progressBarStyleLarge"> + <view + name="ProgressBar (Normal)" + init="" /> + <view + name="ProgressBar (Small)" + init="style=?android:attr/progressBarStyleSmall" /> + <view + name="ProgressBar (Horizontal)" + init="style=?android:attr/progressBarStyleHorizontal" /> + </view> + <view + class="android.widget.SeekBar" + relatedTo="ProgressBar" + fill="width_in_vertical" /> + <view + class="android.widget.QuickContactBadge" /> + <view + class="android.widget.RadioGroup" /> + <view + class="android.widget.RatingBar" /> </category> - <category name="Layouts"> - <view class="android.widget.LinearLayout" fill="opposite" render="skip"/> - <view class="android.widget.RelativeLayout" fill="opposite" render="skip"/> - <view class="android.widget.FrameLayout" fill="opposite" render="skip"/> - <view class="android.widget.TableLayout" fill="opposite" render="skip"/> - <view class="android.widget.TableRow" fill="opposite" render="skip"/> + <category + name="Text Fields"> + <view + class="android.widget.EditText" + name="Plain Text" + init="" + relatedTo="Spinner,TextView,AutoCompleteTextView,MultiAutoCompleteTextView" + fill="width_in_vertical"> + <view + name="Person Name" + init="android:inputType=textPersonName" /> + <view + name="Password" + init="android:inputType=textPassword" /> + <view + name="Password (Numeric)" + init="android:inputType=numberPassword" /> + <view + name="E-mail" + init="android:inputType=textEmailAddress" /> + <view + name="Phone" + init="android:inputType=phone" /> + <view + name="Postal Address" + init="android:inputType=textPostalAddress" /> + <view + name="Multiline Text" + init="android:inputType=textMultiLine" /> + <view + name="Time" + init="android:inputType=time" /> + <view + name="Date" + init="android:inputType=date" /> + <view + name="Number" + init="android:inputType=number" /> + <view + name="Number (Signed)" + init="android:inputType=numberSigned" /> + <view + name="Number (Decimal)" + init="android:inputType=numberDecimal" /> + </view> + <view + class="android.widget.AutoCompleteTextView" + fill="width_in_vertical" /> + <view + class="android.widget.MultiAutoCompleteTextView" + fill="width_in_vertical" /> </category> - <category name="Composite"> - <view class="android.widget.ListView" fill="width_in_vertical" render="skip"/> - <view class="android.widget.ExpandableListView" fill="width_in_vertical" render="skip"/> - <view class="android.widget.TwoLineListItem" render="skip"/> - <view class="android.widget.GridView" fill="opposite" render="skip"/> - <view class="android.widget.ScrollView" fill="opposite" render="skip"/> - <view class="android.widget.HorizontalScrollView" render="skip"/> - <view class="android.widget.SearchView" render="skip"/> - <view class="android.widget.SlidingDrawer"/> - <view class="android.widget.TabHost" fill="width_in_vertical" render="alone" /> - <view class="android.widget.TabWidget" render="alone" /> - <view class="android.webkit.WebView" fill="opposite" render="skip" /> + <category + name="Layouts"> + <view + class="android.widget.LinearLayout" + name="LinearLayout (Vertical)" + init="android:orientation=vertical" + icon="VerticalLinearLayout" + fill="opposite" + render="skip"> + <view + name="LinearLayout (Horizontal)" /> + </view> + <view + class="android.widget.RelativeLayout" + fill="opposite" + render="skip" /> + <view + class="android.widget.FrameLayout" + fill="opposite" + render="skip" /> + <view + class="include" + name="Include Other Layout" + render="skip" /> + <view + class="android.widget.TableLayout" + fill="opposite" + render="skip" /> + <view + class="android.widget.TableRow" + fill="opposite" + render="skip" /> </category> - <category name="Images & Media"> - <view class="android.widget.ImageView" /> - <view class="android.widget.ImageButton" /> - <view class="android.widget.Gallery" fill="width_in_vertical" render="skip"/> - <view class="android.widget.MediaController" render="skip"/> - <view class="android.widget.VideoView" fill="opposite" render="skip"/> + <category + name="Composite"> + <view + class="android.widget.ListView" + relatedTo="ExpandableListView" + fill="width_in_vertical" /> + <view + class="android.widget.ExpandableListView" + relatedTo="ListView" + fill="width_in_vertical" /> + <view + class="android.widget.GridView" + fill="opposite" + render="skip" /> + <view + class="android.widget.ScrollView" + relatedTo="HorizontalScrollView" + fill="opposite" + render="skip" /> + <view + class="android.widget.HorizontalScrollView" + relatedTo="ScrollView" + render="skip" /> + <view + class="android.widget.SearchView" + render="skip" /> + <view + class="android.widget.SlidingDrawer" /> + <view + class="android.widget.TabHost" + fill="width_in_vertical" + render="alone" /> + <view + class="android.widget.TabWidget" + render="alone" /> + <view + class="android.webkit.WebView" + fill="opposite" + render="skip" /> </category> - <category name="Time & Date"> - <view class="android.widget.TimePicker" render="alone"/> - <view class="android.widget.DatePicker" render="alone"/> - <view class="android.widget.CalendarView" /> - <view class="android.widget.Chronometer" /> - <view class="android.widget.AnalogClock" /> - <view class="android.widget.DigitalClock" /> + <category + name="Images & Media"> + <view + class="android.widget.ImageView" + relatedTo="ImageButton,VideoView" /> + <view + class="android.widget.ImageButton" + relatedTo="Button,ImageView" /> + <view + class="android.widget.Gallery" + fill="width_in_vertical" + render="skip" /> + <view + class="android.widget.MediaController" + render="skip" /> + <view + class="android.widget.VideoView" + relatedTo="ImageView" + fill="opposite" + render="skip" /> </category> - <category name="Transitions"> - <view class="android.widget.ImageSwitcher" render="skip"/> - <view class="android.widget.AdapterViewFlipper" fill="opposite" render="skip"/> - <view class="android.widget.StackView" fill="opposite" render="skip"/> - <view class="android.widget.TextSwitcher" fill="opposite" render="skip"/> - <view class="android.widget.ViewAnimator" fill="opposite" render="skip"/> - <view class="android.widget.ViewFlipper" fill="opposite" render="skip"/> - <view class="android.widget.ViewSwitcher" fill="opposite" render="skip"/> + <category + name="Time & Date"> + <view + class="android.widget.TimePicker" + relatedTo="DatePicker,CalendarView" + render="alone" /> + <view + class="android.widget.DatePicker" + relatedTo="TimePicker" + render="alone" /> + <view + class="android.widget.CalendarView" + fill="both" + relatedTo="TimePicker,DatePicker" /> + <view + class="android.widget.Chronometer" + render="skip" /> + <view + class="android.widget.AnalogClock" + relatedTo="DigitalClock" /> + <view + class="android.widget.DigitalClock" + relatedTo="AnalogClock" /> </category> - <category name="Advanced"> - <view class="android.view.View" render="skip"/> - <view class="android.view.ViewStub" render="skip"/> - <view class="android.gesture.GestureOverlayView" render="skip"/> - <view class="android.view.SurfaceView" render="skip"/> - <view class="android.widget.NumberPicker" render="alone"/> - <view class="android.widget.ZoomButton"/> - <view class="android.widget.ZoomControls"/> - <view class="include" skip="true" render="skip"/> - <view class="merge" skip="true" render="skip"/> - <view class="android.widget.DialerFilter" fill="width_in_vertical" render="skip"/> - <view class="android.widget.AbsoluteLayout" fill="opposite" render="skip"/> + <category + name="Transitions"> + <view + class="android.widget.ImageSwitcher" + relatedTo="ViewFlipper,ViewSwitcher,TextSwitcher" + render="skip" /> + <view + class="android.widget.AdapterViewFlipper" + fill="opposite" + render="skip" /> + <view + class="android.widget.StackView" + fill="opposite" + render="skip" /> + <view + class="android.widget.TextSwitcher" + relatedTo="ViewFlipper,ImageSwitcher,ViewSwitcher" + fill="opposite" + render="skip" /> + <view + class="android.widget.ViewAnimator" + fill="opposite" + render="skip" /> + <view + class="android.widget.ViewFlipper" + relatedTo="ViewSwitcher,ImageSwitcher,TextSwitcher" + fill="opposite" + render="skip" /> + <view + class="android.widget.ViewSwitcher" + relatedTo="ViewFlipper,ImageSwitcher,TextSwitcher" + fill="opposite" + render="skip" /> </category> - <category name="Other"> + <category + name="Advanced"> + <view + class="requestFocus" + render="skip" /> + <view + class="android.view.View" + render="skip" /> + <view + class="android.view.ViewStub" + render="skip" /> + <view + class="android.gesture.GestureOverlayView" + render="skip" /> + <view + class="android.view.SurfaceView" + render="skip" /> + <view + class="android.widget.NumberPicker" + relatedTo="TimePicker,DatePicker" + render="alone" /> + <view + class="android.widget.ZoomButton" + relatedTo="Button" /> + <view + class="android.widget.ZoomControls" /> + <view + class="merge" + skip="true" + render="skip" /> + <view + class="android.widget.DialerFilter" + fill="width_in_vertical" + render="skip" /> + <view + class="android.widget.TwoLineListItem" + render="skip" /> + <view + class="android.widget.AbsoluteLayout" + fill="opposite" + render="skip" /> + </category> + <category + name="Other"> <!-- This is the catch-all category which contains unknown views if we encounter any --> </category> <!-- TODO: Add-ons? --> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml index 9c99644..97cda6e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Default configuration for various views to be rendered + Default configuration for various views to be rendered TODO: Remove views that don't have custom configuration TODO: Parameterize the custom width (200dip) in the below? --> @@ -39,24 +39,148 @@ android:layout_height="wrap_content" android:layout_width="wrap_content"> </CheckedTextView> + <!-- <Chronometer android:text="Chronometer" android:id="@+id/android_widget_Chronometer" android:layout_width="wrap_content" android:layout_height="wrap_content"> </Chronometer> + --> <DigitalClock android:text="DigitalClock" android:id="@+id/android_widget_DigitalClock" android:layout_width="wrap_content" android:layout_height="wrap_content"> </DigitalClock> + <EditText - android:id="@+id/android_widget_EditText" - android:layout_height="wrap_content" - android:text="EditText" - android:layout_width="200dip"> + android:id="@+id/PlainText" + android:text="abc" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/Password" + android:inputType="textPassword" + android:text="••••••••" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <!-- android:inputType="numberPassword" not used here to allow digits in preview only --> + <EditText + android:id="@+id/PasswordNumeric" + android:text="1•••2•••3" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/PersonName" + android:inputType="textPersonName" + android:text="Firstname Lastname" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/Phone" + android:inputType="phone" + android:text="(555) 0100" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/PostalAddress" + android:inputType="textPostalAddress" + android:text="Address" + android:layout_width="200dip" + android:layout_height="100dip"> + </EditText> + + <EditText + android:id="@+id/MultilineText" + android:inputType="textMultiLine" + android:text="Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor" + android:layout_width="200dip" + android:layout_height="100dip"> + </EditText> + + <EditText + android:id="@+id/Date" + android:inputType="date" + android:text="1/1/2011" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/Time" + android:inputType="time" + android:text="12:00am" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/Email" + android:inputType="textEmailAddress" + android:text="user@domain" + android:layout_width="200dip" + android:layout_height="wrap_content"> </EditText> + + <EditText + android:id="@+id/Number" + android:inputType="number" + android:text="42" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/NumberSigned" + android:inputType="numberSigned" + android:text="-42" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <EditText + android:id="@+id/NumberDecimal" + android:inputType="numberDecimal" + android:text="42.0" + android:layout_width="200dip" + android:layout_height="wrap_content"> + </EditText> + + <TextView + android:text="Large" + android:id="@+id/LargeText" + android:textAppearance="?android:attr/textAppearanceLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + </TextView> + + <TextView + android:text="Medium" + android:id="@+id/MediumText" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + </TextView> + + <TextView + android:text="Small" + android:id="@+id/SmallText" + android:textAppearance="?android:attr/textAppearanceSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + </TextView> + <ImageButton android:layout_height="wrap_content" android:layout_width="wrap_content" @@ -76,10 +200,29 @@ android:id="@+id/android_widget_MultiAutoCompleteTextView"> </MultiAutoCompleteTextView> <ProgressBar - android:id="@+id/android_widget_ProgressBar" + android:id="@+id/android_widget_ProgressBarNormal" android:layout_width="wrap_content" android:layout_height="wrap_content"> </ProgressBar> + <ProgressBar + android:id="@+id/android_widget_ProgressBarHorizontal" + android:layout_width="200dip" + android:layout_height="wrap_content" + android:progress="30" + style="?android:attr/progressBarStyleHorizontal"> + </ProgressBar> + <ProgressBar + android:id="@+id/android_widget_ProgressBarLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/progressBarStyleLarge"> + </ProgressBar> + <ProgressBar + android:id="@+id/android_widget_ProgressBarSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/progressBarStyleSmall"> + </ProgressBar> <QuickContactBadge android:layout_height="wrap_content" android:layout_width="wrap_content" @@ -104,6 +247,22 @@ android:layout_width="200dip" android:progress="30"> </SeekBar> + <ListView + android:id="@+id/android_widget_ListView" + android:layout_width="200dip" + android:layout_height="60dip" + android:divider="#333333" + android:dividerHeight="1px" + > + </ListView> + <ExpandableListView + android:id="@+id/android_widget_ExpandableListView" + android:layout_width="200dip" + android:layout_height="60dip" + android:divider="#333333" + android:dividerHeight="1px" + > + </ExpandableListView> <Spinner android:layout_height="wrap_content" android:id="@+id/android_widget_Spinner" @@ -143,6 +302,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> </DatePicker> + <CalendarView + android:id="@+id/android_widget_CalendarView" + android:layout_width="200dip" + android:layout_height="200dip"> + </CalendarView> <RadioGroup android:layout_height="wrap_content" android:layout_width="wrap_content" @@ -216,4 +380,4 @@ </FrameLayout> </LinearLayout> </TabHost> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutAction.java new file mode 100644 index 0000000..7029be4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Convert Layout" menu item is invoked. + */ +public class ChangeLayoutAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + ChangeLayoutRefactoring ref = new ChangeLayoutRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new ChangeLayoutWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Change Layout...", editor, ChangeLayoutAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutContribution.java new file mode 100644 index 0000000..c508b7e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutContribution.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class ChangeLayoutContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new ChangeLayoutRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof ChangeLayoutRefactoring.Descriptor) { + return ((ChangeLayoutRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java new file mode 100644 index 0000000..fc3b21f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_BASELINE_ALIGNED; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_BELOW; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; +import static com.android.ide.common.layout.LayoutConstants.FQCN_GESTURE_OVERLAY_VIEW; +import static com.android.ide.common.layout.LayoutConstants.FQCN_LINEAR_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.FQCN_RELATIVE_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.FQCN_TABLE_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW; +import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.TABLE_ROW; +import static com.android.ide.common.layout.LayoutConstants.VALUE_FALSE; +import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ViewHierarchy; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Converts the selected layout into a layout of a different type. + */ +@SuppressWarnings("restriction") // XML model +public class ChangeLayoutRefactoring extends VisualRefactoring { + private static final String KEY_TYPE = "type"; //$NON-NLS-1$ + private static final String KEY_FLATTEN = "flatten"; //$NON-NLS-1$ + + private String mTypeFqcn; + private boolean mFlatten; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ChangeLayoutRefactoring(Map<String, String> arguments) { + super(arguments); + mTypeFqcn = arguments.get(KEY_TYPE); + mFlatten = Boolean.parseBoolean(arguments.get(KEY_FLATTEN)); + } + + @VisibleForTesting + ChangeLayoutRefactoring(List<Element> selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + public ChangeLayoutRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 2); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to convert"); + return status; + } + + if (mElements.size() != 1) { + status.addFatalError("Select precisely one layout to convert"); + return status; + } + + pm.worked(1); + return status; + + } finally { + pm.done(); + } + } + + @Override + protected VisualRefactoringDescriptor createDescriptor() { + String comment = getName(); + return new Descriptor( + mProject.getName(), //project + comment, //description + comment, //comment + createArgumentMap()); + } + + @Override + protected Map<String, String> createArgumentMap() { + Map<String, String> args = super.createArgumentMap(); + args.put(KEY_TYPE, mTypeFqcn); + args.put(KEY_FLATTEN, Boolean.toString(mFlatten)); + + return args; + } + + @Override + public String getName() { + return "Change Layout"; + } + + void setType(String typeFqcn) { + mTypeFqcn = typeFqcn; + } + + void setFlatten(boolean flatten) { + mFlatten = flatten; + } + + @Override + protected List<Element> initElements() { + List<Element> elements = super.initElements(); + + // Don't convert a root GestureOverlayView; convert its child. This looks for + // gesture overlays, and if found, it generates a new child list where the gesture + // overlay children are replaced by their first element children + for (Element element : elements) { + String tagName = element.getTagName(); + if (tagName.equals(GESTURE_OVERLAY_VIEW) + || tagName.equals(FQCN_GESTURE_OVERLAY_VIEW)) { + List<Element> replacement = new ArrayList<Element>(elements.size()); + for (Element e : elements) { + tagName = e.getTagName(); + if (tagName.equals(GESTURE_OVERLAY_VIEW) + || tagName.equals(FQCN_GESTURE_OVERLAY_VIEW)) { + NodeList children = e.getChildNodes(); + Element first = null; + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + first = (Element) node; + break; + } + } + if (first != null) { + e = first; + } + } + replacement.add(e); + } + return replacement; + } + } + + return elements; + } + + @Override + protected List<Change> computeChanges(IProgressMonitor monitor) { + String name = getViewClass(mTypeFqcn); + + IFile file = mEditor.getInputFile(); + List<Change> changes = new ArrayList<Change>(); + TextFileChange change = new TextFileChange(file.getName(), file); + MultiTextEdit rootEdit = new MultiTextEdit(); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + + String text = getText(mSelectionStart, mSelectionEnd); + Element layout = getPrimaryElement(); + String oldName = layout.getNodeName(); + int open = text.indexOf(oldName); + int close = text.lastIndexOf(oldName); + + if (open != -1 && close != -1) { + int oldLength = oldName.length(); + rootEdit.addChild(new ReplaceEdit(mSelectionStart + open, oldLength, name)); + if (close != open) { // Gracefully handle <FooLayout/> + rootEdit.addChild(new ReplaceEdit(mSelectionStart + close, oldLength, name)); + } + } + + String oldId = getId(layout); + String newId = ensureIdMatchesType(layout, mTypeFqcn, rootEdit); + // Update any layout references to the old id with the new id + if (oldId != null && newId != null) { + IStructuredModel model = mEditor.getModelForRead(); + try { + IStructuredDocument doc = model.getStructuredDocument(); + if (doc != null) { + List<TextEdit> replaceIds = replaceIds(getAndroidNamespacePrefix(), doc, + mSelectionStart, + mSelectionEnd, oldId, newId); + for (TextEdit edit : replaceIds) { + rootEdit.addChild(edit); + } + } + } finally { + model.releaseFromRead(); + } + } + + String oldType = getOldType(); + String newType = mTypeFqcn; + if (newType.equals(FQCN_RELATIVE_LAYOUT)) { + if (oldType.equals(FQCN_LINEAR_LAYOUT) && !mFlatten) { + // Hand-coded conversion specifically tailored for linear to relative, provided + // there is no hierarchy flattening + // TODO: use the RelativeLayoutConversionHelper for this; it does a better job + // analyzing gravities etc. + convertLinearToRelative(rootEdit); + removeUndefinedLayoutAttrs(rootEdit, layout); + } else { + // Generic conversion to relative - can also flatten the hierarchy + convertAnyToRelative(rootEdit); + // This already handles removing undefined layout attributes -- right? + //removeUndefinedLayoutAttrs(rootEdit, layout); + } + } else if (oldType.equals(FQCN_RELATIVE_LAYOUT) && newType.equals(FQCN_LINEAR_LAYOUT)) { + convertRelativeToLinear(rootEdit); + removeUndefinedLayoutAttrs(rootEdit, layout); + } else if (oldType.equals(FQCN_LINEAR_LAYOUT) && newType.equals(FQCN_TABLE_LAYOUT)) { + convertLinearToTable(rootEdit); + removeUndefinedLayoutAttrs(rootEdit, layout); + } else { + convertGeneric(rootEdit, oldType, newType); + removeUndefinedLayoutAttrs(rootEdit, layout); + } + + return changes; + } + + /** Hand coded conversion from a LinearLayout to a TableLayout */ + private void convertLinearToTable(MultiTextEdit rootEdit) { + // This is pretty easy; just switch the root tag (already done by the initial generic + // conversion) and then convert all the children into <TableRow> elements. + // Finally, get rid of the orientation attribute, if any. + Element layout = getPrimaryElement(); + removeOrientationAttribute(rootEdit, layout); + + NodeList children = layout.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + if (node instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) node; + int start = region.getStartOffset(); + int end = region.getEndOffset(); + String text = getText(start, end); + String oldName = child.getNodeName(); + if (oldName.equals(LINEAR_LAYOUT)) { + removeOrientationAttribute(rootEdit, child); + int open = text.indexOf(oldName); + int close = text.lastIndexOf(oldName); + + if (open != -1 && close != -1) { + int oldLength = oldName.length(); + rootEdit.addChild(new ReplaceEdit(mSelectionStart + open, oldLength, + TABLE_ROW)); + if (close != open) { // Gracefully handle <FooLayout/> + rootEdit.addChild(new ReplaceEdit(mSelectionStart + close, + oldLength, TABLE_ROW)); + } + } + } // else: WRAP in TableLayout! + } + } + } + } + + /** Hand coded conversion from a LinearLayout to a RelativeLayout */ + private void convertLinearToRelative(MultiTextEdit rootEdit) { + // This can be done accurately. + Element layout = getPrimaryElement(); + // Horizontal is the default, so if no value is specified it is horizontal. + boolean isVertical = VALUE_VERTICAL.equals(layout.getAttributeNS(ANDROID_URI, + ATTR_ORIENTATION)); + + removeOrientationAttribute(rootEdit, layout); + + String attributePrefix = getAndroidNamespacePrefix(); + + // TODO: Consider gravity of each element + // TODO: Consider weight of each element + // Right now it simply makes a single attachment to keep the order. + + if (isVertical) { + // Align each child to the bottom and left of its parent + NodeList children = layout.getChildNodes(); + String prevId = null; + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + String id = ensureHasId(rootEdit, child, null); + if (prevId != null) { + setAttribute(rootEdit, child, ANDROID_URI, attributePrefix, + ATTR_LAYOUT_BELOW, prevId); + } + prevId = id; + } + } + } else { + // Align each child to the left + NodeList children = layout.getChildNodes(); + boolean isBaselineAligned = + !VALUE_FALSE.equals(layout.getAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED)); + + String prevId = null; + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + String id = ensureHasId(rootEdit, child, null); + if (prevId != null) { + setAttribute(rootEdit, child, ANDROID_URI, attributePrefix, + ATTR_LAYOUT_TO_RIGHT_OF, prevId); + if (isBaselineAligned) { + setAttribute(rootEdit, child, ANDROID_URI, attributePrefix, + ATTR_LAYOUT_ALIGN_BASELINE, prevId); + } + } + prevId = id; + } + } + } + } + + /** Strips out the android:orientation attribute from the given linear layout element */ + private void removeOrientationAttribute(MultiTextEdit rootEdit, Element layout) { + assert layout.getTagName().equals(LINEAR_LAYOUT); + removeAttribute(rootEdit, layout, ANDROID_URI, ATTR_ORIENTATION); + } + + /** + * Hand coded conversion from a RelativeLayout to a LinearLayout + * + * @param rootEdit the root multi text edit to add edits to + */ + private void convertRelativeToLinear(MultiTextEdit rootEdit) { + // This is going to be lossy... + // TODO: Attempt to "order" the items based on their visual positions + // and insert them in that order in the LinearLayout. + // TODO: Possibly use nesting if necessary, by spatial subdivision, + // to accomplish roughly the same layout as the relative layout specifies. + } + + /** + * Hand coded -generic- conversion from one layout to another. This is not going to be + * an accurate layout transformation; instead it simply migrates the layout attributes + * that are supported, and adds defaults for any new required layout attributes. In + * addition, it attempts to order the children visually based on where they fit in a + * rendering. (Unsupported layout attributes will be removed by the caller at the + * end.) + * <ul> + * <li>Try to handle nesting. Converting a *hierarchy* of layouts into a flatter + * layout for powerful layouts that support it, like RelativeLayout. + * <li>Try to do automatic "inference" about the layout. I can render it and look at + * the ViewInfo positions and sizes. I can render it multiple times, at different + * sizes, to infer "stretchiness" and "weight" properties of the children. + * <li>Try to do indirect transformations. E.g. if I can go from A to B, and B to C, + * then an attempt to go from A to C should perform conversions A to B and then B to + * C. + * </ul> + * + * @param rootEdit the root multi text edit to add edits to + * @param oldType the fully qualified class name of the layout type we are converting + * from + * @param newType the fully qualified class name of the layout type we are converting + * to + */ + private void convertGeneric(MultiTextEdit rootEdit, String oldType, String newType) { + // TODO: Add hooks for 3rd party conversions getting registered through the + // IViewRule interface. + + // For now we simply go with the default behavior, which is to just strip the + // layout attributes that aren't supported. + } + + /** Removes all the unused attributes after a conversion */ + private void removeUndefinedLayoutAttrs(MultiTextEdit rootEdit, Element layout) { + ViewElementDescriptor descriptor = getElementDescriptor(mTypeFqcn); + if (descriptor == null) { + return; + } + + Set<String> defined = new HashSet<String>(); + AttributeDescriptor[] layoutAttributes = descriptor.getLayoutAttributes(); + for (AttributeDescriptor attribute : layoutAttributes) { + defined.add(attribute.getXmlLocalName()); + } + + NodeList children = layout.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + + List<Attr> attributes = findLayoutAttributes(child); + for (Attr attribute : attributes) { + String name = attribute.getLocalName(); + if (!defined.contains(name)) { + // Remove it + removeAttribute(rootEdit, child, attribute.getNamespaceURI(), name); + } + } + } + } + } + + /** Hand coded conversion from any layout to a RelativeLayout */ + private void convertAnyToRelative(MultiTextEdit rootEdit) { + // To perform a conversion from any other layout type, including nested conversion, + Element layout = getPrimaryElement(); + CanvasViewInfo rootView = mRootView; + if (rootView == null) { + LayoutCanvas canvas = mEditor.getGraphicalEditor().getCanvasControl(); + ViewHierarchy viewHierarchy = canvas.getViewHierarchy(); + rootView = viewHierarchy.getRoot(); + } + + RelativeLayoutConversionHelper helper = + new RelativeLayoutConversionHelper(this, layout, mFlatten, rootEdit, rootView); + helper.convertToRelative(); + } + + public static class Descriptor extends VisualRefactoringDescriptor { + public Descriptor(String project, String description, String comment, + Map<String, String> arguments) { + super("com.android.ide.eclipse.adt.refactoring.convert", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new ChangeLayoutRefactoring(args); + } + } + + String getOldType() { + Element primary = getPrimaryElement(); + if (primary != null) { + String oldType = primary.getTagName(); + if (oldType.indexOf('.') == -1) { + oldType = ANDROID_WIDGET_PREFIX + oldType; + } + return oldType; + } + + return null; + } + + @VisibleForTesting + protected CanvasViewInfo mRootView; + + @VisibleForTesting + public void setRootView(CanvasViewInfo rootView) { + mRootView = rootView; + } + + @Override + VisualRefactoringWizard createWizard() { + return new ChangeLayoutWizard(this, mEditor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutWizard.java new file mode 100644 index 0000000..04da01d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutWizard.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.FQCN_RELATIVE_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.RELATIVE_LAYOUT; +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.util.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +class ChangeLayoutWizard extends VisualRefactoringWizard { + + public ChangeLayoutWizard(ChangeLayoutRefactoring ref, LayoutEditor editor) { + super(ref, editor); + setDefaultPageTitle("Change Layout"); + } + + @Override + protected void addUserInputPages() { + ChangeLayoutRefactoring ref = (ChangeLayoutRefactoring) getRefactoring(); + String oldType = ref.getOldType(); + addPage(new InputPage(mEditor.getProject(), oldType)); + } + + /** Wizard page which inputs parameters for the {@link ChangeLayoutRefactoring} operation */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private final String mOldType; + private Combo mTypeCombo; + private Button mFlatten; + private List<Pair<String, ViewElementDescriptor>> mClassNames; + + public InputPage(IProject project, String oldType) { + super("ChangeLayoutInputPage"); //$NON-NLS-1$ + mProject = project; + mOldType = oldType; + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label fromLabel = new Label(composite, SWT.NONE); + fromLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + String oldTypeBase = mOldType.substring(mOldType.lastIndexOf('.') + 1); + fromLabel.setText(String.format("Change from %1$s", oldTypeBase)); + + Label typeLabel = new Label(composite, SWT.NONE); + typeLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + typeLabel.setText("New Layout Type:"); + + mTypeCombo = new Combo(composite, SWT.READ_ONLY); + mTypeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + SelectionAdapter selectionListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + validatePage(); + // Hierarchy flattening only works for relative layout (and any future + // layouts that can also support arbitrary layouts). + mFlatten.setVisible(mTypeCombo.getText().equals(FQCN_RELATIVE_LAYOUT)); + } + }; + mTypeCombo.addSelectionListener(selectionListener); + mTypeCombo.addSelectionListener(mSelectionValidateListener); + + mFlatten = new Button(composite, SWT.CHECK); + mFlatten.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, + false, false, 2, 1)); + mFlatten.setText("Flatten hierarchy"); + mFlatten.addSelectionListener(selectionListener); + // Should flattening be selected by default? + mFlatten.setSelection(true); + mFlatten.addSelectionListener(mSelectionValidateListener); + + // We don't exclude RelativeLayout even if the current layout is RelativeLayout, + // in case you are trying to flatten the hierarchy for a hierarchy that has a + // RelativeLayout at the root. + Set<String> exclude = new HashSet<String>(); + exclude.add(VIEW_INCLUDE); + boolean oldIsRelativeLayout = mOldType.equals(FQCN_RELATIVE_LAYOUT); + if (oldIsRelativeLayout) { + exclude.add(mOldType); + } + mClassNames = WrapInWizard.addLayouts(mProject, mOldType, mTypeCombo, exclude, false); + + mTypeCombo.select(0); + // The default should be Relative layout, if available (and not the old Type) + if (!oldIsRelativeLayout) { + for (int i = 0; i < mTypeCombo.getItemCount(); i++) { + if (mTypeCombo.getItem(i).equals(RELATIVE_LAYOUT)) { + mTypeCombo.select(i); + break; + } + } + } + mFlatten.setVisible(mTypeCombo.getText().equals(RELATIVE_LAYOUT)); + + setControl(composite); + validatePage(); + } + + @Override + protected boolean validatePage() { + boolean ok = true; + + int selectionIndex = mTypeCombo.getSelectionIndex(); + String type = selectionIndex != -1 ? mClassNames.get(selectionIndex).getFirst() : null; + if (type == null) { + setErrorMessage("Select a layout type"); + ok = false; // The user has chosen a separator + } else { + setErrorMessage(null); + + // Record state + ChangeLayoutRefactoring refactoring = + (ChangeLayoutRefactoring) getRefactoring(); + refactoring.setType(type); + refactoring.setFlatten(mFlatten.getSelection()); + } + + setPageComplete(ok); + return ok; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewAction.java new file mode 100644 index 0000000..1d9ea86 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Change View Type" menu item is invoked. + */ +public class ChangeViewAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + ChangeViewRefactoring ref = new ChangeViewRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new ChangeViewWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Change Widget Type...", editor, ChangeViewAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewContribution.java new file mode 100644 index 0000000..7705ed8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewContribution.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class ChangeViewContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new ChangeViewRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof ChangeViewRefactoring.Descriptor) { + return ((ChangeViewRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java new file mode 100644 index 0000000..2e92e48 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoring.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Changes the type of the given widgets to the given target type + * and updates the attributes if necessary + */ +@SuppressWarnings("restriction") // XML model +public class ChangeViewRefactoring extends VisualRefactoring { + private static final String KEY_TYPE = "type"; //$NON-NLS-1$ + private String mTypeFqcn; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ChangeViewRefactoring(Map<String, String> arguments) { + super(arguments); + mTypeFqcn = arguments.get(KEY_TYPE); + } + + public ChangeViewRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @VisibleForTesting + ChangeViewRefactoring(List<Element> selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 6); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to convert"); + return status; + } + + // Make sure the selection is contiguous + if (mTreeSelection != null) { + List<CanvasViewInfo> infos = getSelectedViewInfos(); + if (!validateNotEmpty(infos, status)) { + return status; + } + } + + // Ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to convert"); + return status; + } + + pm.worked(1); + return status; + + } finally { + pm.done(); + } + } + + @Override + protected VisualRefactoringDescriptor createDescriptor() { + String comment = getName(); + return new Descriptor( + mProject.getName(), //project + comment, //description + comment, //comment + createArgumentMap()); + } + + @Override + protected Map<String, String> createArgumentMap() { + Map<String, String> args = super.createArgumentMap(); + args.put(KEY_TYPE, mTypeFqcn); + + return args; + } + + @Override + public String getName() { + return "Change Widget Type"; + } + + void setType(String typeFqcn) { + mTypeFqcn = typeFqcn; + } + + @Override + protected List<Change> computeChanges(IProgressMonitor monitor) { + String name = getViewClass(mTypeFqcn); + + IFile file = mEditor.getInputFile(); + List<Change> changes = new ArrayList<Change>(); + TextFileChange change = new TextFileChange(file.getName(), file); + MultiTextEdit rootEdit = new MultiTextEdit(); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + + for (Element element : getElements()) { + IndexedRegion region = getRegion(element); + String text = getText(region.getStartOffset(), region.getEndOffset()); + String oldName = element.getNodeName(); + int open = text.indexOf(oldName); + int close = text.lastIndexOf(oldName); + + if (open != -1 && close != -1) { + int oldLength = oldName.length(); + rootEdit.addChild(new ReplaceEdit(region.getStartOffset() + open, + oldLength, name)); + if (close != open) { // Gracefully handle <FooLayout/> + rootEdit.addChild(new ReplaceEdit(region.getStartOffset() + close, oldLength, + name)); + } + } + + // Change tag type + String oldId = getId(element); + String newId = ensureIdMatchesType(element, mTypeFqcn, rootEdit); + // Update any layout references to the old id with the new id + if (oldId != null && newId != null) { + IStructuredModel model = mEditor.getModelForRead(); + try { + IStructuredDocument doc = model.getStructuredDocument(); + if (doc != null) { + IndexedRegion range = getRegion(element); + int skipStart = range.getStartOffset(); + int skipEnd = range.getEndOffset(); + List<TextEdit> replaceIds = replaceIds(getAndroidNamespacePrefix(), doc, + skipStart, skipEnd, + oldId, newId); + for (TextEdit edit : replaceIds) { + rootEdit.addChild(edit); + } + } + } finally { + model.releaseFromRead(); + } + } + + // Strip out attributes that no longer make sense + removeUndefinedAttrs(rootEdit, element); + } + + return changes; + } + + /** Removes all the unused attributes after a conversion */ + private void removeUndefinedAttrs(MultiTextEdit rootEdit, Element element) { + ViewElementDescriptor descriptor = getElementDescriptor(mTypeFqcn); + if (descriptor == null) { + return; + } + + Set<String> defined = new HashSet<String>(); + AttributeDescriptor[] layoutAttributes = descriptor.getAttributes(); + for (AttributeDescriptor attribute : layoutAttributes) { + defined.add(attribute.getXmlLocalName()); + } + + List<Attr> attributes = findAttributes(element); + for (Attr attribute : attributes) { + String name = attribute.getLocalName(); + if (!defined.contains(name)) { + // Remove it + removeAttribute(rootEdit, element, attribute.getNamespaceURI(), name); + } + } + + // Set text attribute if it's defined + if (defined.contains(ATTR_TEXT) && !element.hasAttributeNS(ANDROID_URI, ATTR_TEXT)) { + setAttribute(rootEdit, element, ANDROID_URI, getAndroidNamespacePrefix(), + ATTR_TEXT, descriptor.getUiName()); + } + } + + protected List<Attr> findAttributes(Node root) { + List<Attr> result = new ArrayList<Attr>(); + NamedNodeMap attributes = root.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node attributeNode = attributes.item(i); + + String name = attributeNode.getLocalName(); + if (!name.startsWith(ATTR_LAYOUT_PREFIX) + && ANDROID_URI.equals(attributeNode.getNamespaceURI())) { + result.add((Attr) attributeNode); + } + } + + return result; + } + + List<String> getOldTypes() { + List<String> types = new ArrayList<String>(); + for (Element primary : getElements()) { + String oldType = primary.getTagName(); + if (oldType.indexOf('.') == -1) { + oldType = ANDROID_WIDGET_PREFIX + oldType; + } + types.add(oldType); + } + + return types; + } + + @Override + VisualRefactoringWizard createWizard() { + return new ChangeViewWizard(this, mEditor); + } + + public static class Descriptor extends VisualRefactoringDescriptor { + public Descriptor(String project, String description, String comment, + Map<String, String> arguments) { + super("com.android.ide.eclipse.adt.refactoring.changeview", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new ChangeViewRefactoring(args); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewWizard.java new file mode 100644 index 0000000..1372006 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewWizard.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CustomViewFinder; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.sdklib.IAndroidTarget; +import com.android.util.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import java.util.ArrayList; +import java.util.List; + +class ChangeViewWizard extends VisualRefactoringWizard { + private static final String SEPARATOR_LABEL = + "----------------------------------------"; //$NON-NLS-1$ + + public ChangeViewWizard(ChangeViewRefactoring ref, LayoutEditor editor) { + super(ref, editor); + setDefaultPageTitle("Change Widget Type"); + } + + @Override + protected void addUserInputPages() { + ChangeViewRefactoring ref = (ChangeViewRefactoring) getRefactoring(); + List<String> oldTypes = ref.getOldTypes(); + String oldType = null; + for (String type : oldTypes) { + if (oldType == null) { + oldType = type; + } else if (!oldType.equals(type)) { + // If the types differ, don't offer related categories + oldType = null; + break; + } + } + addPage(new InputPage(mEditor.getProject(), oldType)); + } + + /** Wizard page which inputs parameters for the {@link ChangeViewRefactoring} operation */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private Combo mTypeCombo; + private final String mOldType; + private List<String> mClassNames; + + public InputPage(IProject project, String oldType) { + super("ChangeViewInputPage"); //$NON-NLS-1$ + mProject = project; + mOldType = oldType; + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label typeLabel = new Label(composite, SWT.NONE); + typeLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + typeLabel.setText("New Widget Type:"); + + mTypeCombo = new Combo(composite, SWT.READ_ONLY); + mTypeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mTypeCombo.addSelectionListener(mSelectionValidateListener); + + mClassNames = getWidgetTypes(mOldType, mTypeCombo); + mTypeCombo.select(0); + + setControl(composite); + validatePage(); + + mTypeCombo.setFocus(); + } + + private List<String> getWidgetTypes(String oldType, Combo combo) { + List<String> classNames = new ArrayList<String>(); + + // Populate type combo + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mProject); + if (target != null) { + // Try to pick "related" widgets to the one you have selected. + // For example, for an AnalogClock, display DigitalClock first. + // For a Text, offer EditText, AutoComplete, etc. + if (oldType != null) { + ViewMetadataRepository repository = ViewMetadataRepository.get(); + List<String> relatedTo = repository.getRelatedTo(oldType); + if (relatedTo.size() > 0) { + for (String className : relatedTo) { + String base = className.substring(className.lastIndexOf('.') + 1); + combo.add(base); + classNames.add(className); + } + combo.add(SEPARATOR_LABEL); + classNames.add(null); + } + } + + Pair<List<String>,List<String>> result = + CustomViewFinder.findViews(mProject, false); + List<String> customViews = result.getFirst(); + List<String> thirdPartyViews = result.getSecond(); + if (customViews.size() > 0) { + for (String view : customViews) { + combo.add(view); + classNames.add(view); + } + combo.add(SEPARATOR_LABEL); + classNames.add(null); + } + + if (thirdPartyViews.size() > 0) { + for (String view : thirdPartyViews) { + combo.add(view); + classNames.add(view); + } + combo.add(SEPARATOR_LABEL); + classNames.add(null); + } + + AndroidTargetData targetData = currentSdk.getTargetData(target); + if (targetData != null) { + // Now add ALL known layout descriptors in case the user has + // a special case + List<ViewElementDescriptor> descriptors = + targetData.getLayoutDescriptors().getViewDescriptors(); + for (ViewElementDescriptor d : descriptors) { + String className = d.getFullClassName(); + if (className.equals(LayoutDescriptors.VIEW_INCLUDE)) { + continue; + } + combo.add(d.getUiName()); + classNames.add(className); + + } + } + } + } else { + combo.add("SDK not initialized"); + classNames.add(null); + } + + return classNames; + } + + @Override + protected boolean validatePage() { + boolean ok = true; + int selectionIndex = mTypeCombo.getSelectionIndex(); + String type = selectionIndex != -1 ? mClassNames.get(selectionIndex) : null; + if (type == null) { + setErrorMessage("Select a widget type to convert to"); + ok = false; // The user has chosen a separator + } else { + setErrorMessage(null); + } + + // Record state + ChangeViewRefactoring refactoring = + (ChangeViewRefactoring) getRefactoring(); + refactoring.setType(type); + + setPageComplete(ok); + return ok; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeAction.java new file mode 100644 index 0000000..09cb044 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Extract as Include" menu item is invoked. + */ +public class ExtractIncludeAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + ExtractIncludeRefactoring ref = new ExtractIncludeRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new ExtractIncludeWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Extract Include...", editor, ExtractIncludeAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeContribution.java new file mode 100644 index 0000000..5903812 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeContribution.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class ExtractIncludeContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new ExtractIncludeRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof ExtractIncludeRefactoring.Descriptor) { + return ((ExtractIncludeRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java new file mode 100644 index 0000000..7def205 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.AndroidConstants.FD_RES_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; +import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON; +import static com.android.resources.ResourceType.LAYOUT; +import static com.android.sdklib.SdkConstants.FD_RES; + +import com.android.AndroidConstants; +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.sdklib.SdkConstants; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.NullChange; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.swt.widgets.Display; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Extracts the selection and writes it out as a separate layout file, then adds an + * include to that new layout file. Interactively asks the user for a new name for the + * layout. + */ +@SuppressWarnings("restriction") // XML model +public class ExtractIncludeRefactoring extends VisualRefactoring { + private static final String KEY_NAME = "name"; //$NON-NLS-1$ + private static final String KEY_OCCURRENCES = "all-occurrences"; //$NON-NLS-1$ + private String mLayoutName; + private boolean mReplaceOccurrences; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ExtractIncludeRefactoring(Map<String, String> arguments) { + super(arguments); + mLayoutName = arguments.get(KEY_NAME); + mReplaceOccurrences = Boolean.parseBoolean(arguments.get(KEY_OCCURRENCES)); + } + + public ExtractIncludeRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @VisibleForTesting + ExtractIncludeRefactoring(List<Element> selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 6); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to extract"); + return status; + } + + // Make sure the selection is contiguous + if (mTreeSelection != null) { + // TODO - don't do this if we based the selection on text. In this case, + // make sure we're -balanced-. + List<CanvasViewInfo> infos = getSelectedViewInfos(); + if (!validateNotEmpty(infos, status)) { + return status; + } + + if (!validateNotRoot(infos, status)) { + return status; + } + + // Disable if you've selected a single include tag + if (infos.size() == 1) { + UiViewElementNode uiNode = infos.get(0).getUiViewNode(); + if (uiNode != null) { + Node xmlNode = uiNode.getXmlNode(); + if (xmlNode.getLocalName().equals(LayoutDescriptors.VIEW_INCLUDE)) { + status.addWarning("No point in refactoring a single include tag"); + } + } + } + + // Enforce that the selection is -contiguous- + if (!validateContiguous(infos, status)) { + return status; + } + } + + // This also ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to extract"); + return status; + } + + pm.worked(1); + return status; + + } finally { + pm.done(); + } + } + + @Override + protected VisualRefactoringDescriptor createDescriptor() { + String comment = getName(); + return new Descriptor( + mProject.getName(), //project + comment, //description + comment, //comment + createArgumentMap()); + } + + @Override + protected Map<String, String> createArgumentMap() { + Map<String, String> args = super.createArgumentMap(); + args.put(KEY_NAME, mLayoutName); + args.put(KEY_OCCURRENCES, Boolean.toString(mReplaceOccurrences)); + + return args; + } + + @Override + public String getName() { + return "Extract as Include"; + } + + void setLayoutName(String layoutName) { + mLayoutName = layoutName; + } + + void setReplaceOccurrences(boolean selection) { + mReplaceOccurrences = selection; + } + + // ---- Actual implementation of Extract as Include modification computation ---- + + @Override + protected List<Change> computeChanges(IProgressMonitor monitor) { + String extractedText = getExtractedText(); + + String namespaceDeclarations = computeNamespaceDeclarations(); + + // Insert namespace: + extractedText = insertNamespace(extractedText, namespaceDeclarations); + + StringBuilder sb = new StringBuilder(); + sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$ + sb.append(extractedText); + sb.append('\n'); + + List<Change> changes = new ArrayList<Change>(); + + String newFileName = mLayoutName + DOT_XML; + IProject project = mEditor.getProject(); + IFile sourceFile = mEditor.getInputFile(); + + // Replace extracted elements by <include> tag + handleIncludingFile(changes, sourceFile, mSelectionStart, mSelectionEnd, + getDomDocument(), getPrimaryElement()); + + // Also extract in other variations of the same file (landscape/portrait, etc) + boolean haveVariations = false; + if (mReplaceOccurrences) { + List<IFile> layouts = getOtherLayouts(sourceFile); + for (IFile file : layouts) { + IModelManager modelManager = StructuredModelManager.getModelManager(); + IStructuredModel model = null; + // We could enhance this with a SubMonitor to make the progress bar move as + // well. + monitor.subTask(String.format("Looking for duplicates in %1$s", + file.getProjectRelativePath())); + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + + try { + model = modelManager.getModelForRead(file); + if (model instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) model; + IDOMDocument otherDocument = domModel.getDocument(); + List<Element> otherElements = new ArrayList<Element>(); + Element otherPrimary = null; + + for (Element element : getElements()) { + Element other = DomUtilities.findCorresponding(element, + otherDocument); + if (other != null) { + // See if the structure is similar to what we have in this + // document + if (DomUtilities.isEquivalent(element, other)) { + otherElements.add(other); + if (element == getPrimaryElement()) { + otherPrimary = other; + } + } + } + } + + // Only perform extract in the other file if we find a match for + // ALL of elements being extracted, and if they too are contiguous + if (otherElements.size() == getElements().size() && + DomUtilities.isContiguous(otherElements)) { + // Find the range + int begin = Integer.MAX_VALUE; + int end = Integer.MIN_VALUE; + for (Element element : otherElements) { + // Yes!! Extract this one as well! + IndexedRegion region = getRegion(element); + end = Math.max(end, region.getEndOffset()); + begin = Math.min(begin, region.getStartOffset()); + } + handleIncludingFile(changes, file, begin, + end, otherDocument, otherPrimary); + haveVariations = true; + } + } + } catch (IOException e) { + AdtPlugin.log(e, null); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + } + } + + // Add change to create the new file + IContainer parent = sourceFile.getParent(); + if (haveVariations) { + // If we're extracting from multiple configuration folders, then we need to + // place the extracted include in the base layout folder (if not it goes next to + // the including file) + parent = mProject.getFolder(FD_RES).getFolder(FD_RES_LAYOUT); + } + IPath parentPath = parent.getProjectRelativePath(); + final IFile file = project.getFile(new Path(parentPath + WS_SEP + newFileName)); + TextFileChange addFile = new TextFileChange("Create new separate layout", file); + addFile.setTextType(AdtConstants.EXT_XML); + changes.add(addFile); + addFile.setEdit(new InsertEdit(0, sb.toString())); + + Change finishHook = createFinishHook(file); + changes.add(finishHook); + + return changes; + } + + private void handleIncludingFile(List<Change> changes, + IFile sourceFile, int begin, int end, Document document, Element primary) { + TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile); + MultiTextEdit rootEdit = new MultiTextEdit(); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + + String referenceId = getReferenceId(); + // Replace existing elements in the source file and insert <include> + String androidNsPrefix = getAndroidNamespacePrefix(document); + String include = computeIncludeString(primary, mLayoutName, androidNsPrefix, referenceId); + int length = end - begin; + ReplaceEdit replace = new ReplaceEdit(begin, length, include); + rootEdit.addChild(replace); + + // Update any layout references to the old id with the new id + if (referenceId != null && primary != null) { + String rootId = getId(primary); + IStructuredModel model = null; + try { + model = StructuredModelManager.getModelManager().getModelForRead(sourceFile); + IStructuredDocument doc = model.getStructuredDocument(); + if (doc != null && rootId != null) { + List<TextEdit> replaceIds = replaceIds(androidNsPrefix, doc, begin, + end, rootId, referenceId); + for (TextEdit edit : replaceIds) { + rootEdit.addChild(edit); + } + } + } catch (IOException e) { + AdtPlugin.log(e, null); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + } + } + + /** + * Returns a list of all the other layouts (in all configurations) in the project other + * than the given source layout where the refactoring was initiated. Never null. + */ + private List<IFile> getOtherLayouts(IFile sourceFile) { + List<IFile> layouts = new ArrayList<IFile>(100); + IPath sourcePath = sourceFile.getProjectRelativePath(); + IFolder resources = mProject.getFolder(SdkConstants.FD_RESOURCES); + try { + for (IResource folder : resources.members()) { + assert resources != null; + if (folder.getName().startsWith(AndroidConstants.FD_RES_LAYOUT) && + folder instanceof IFolder) { + IFolder layoutFolder = (IFolder) folder; + for (IResource file : layoutFolder.members()) { + if (file.getName().endsWith(EXT_XML) + && file instanceof IFile) { + if (!file.getProjectRelativePath().equals(sourcePath)) { + layouts.add((IFile) file); + } + } + } + } + } + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + return layouts; + } + + String getInitialName() { + String defaultName = ""; //$NON-NLS-1$ + Element primary = getPrimaryElement(); + if (primary != null) { + String id = primary.getAttributeNS(ANDROID_URI, ATTR_ID); + // id null check for https://bugs.eclipse.org/bugs/show_bug.cgi?id=272378 + if (id != null && (id.startsWith(ID_PREFIX) || id.startsWith(NEW_ID_PREFIX))) { + // Use everything following the id/, and make it lowercase since that is + // the convention for layouts + defaultName = id.substring(id.indexOf('/') + 1).toLowerCase(); + + IInputValidator validator = ResourceNameValidator.create(true, mProject, LAYOUT); + + if (validator.isValid(defaultName) != null) { // Already exists? + defaultName = ""; //$NON-NLS-1$ + } + } + } + + return defaultName; + } + + IFile getSourceFile() { + return mFile; + } + + private Change createFinishHook(final IFile file) { + return new NullChange("Open extracted layout and refresh resources") { + @Override + public Change perform(IProgressMonitor pm) throws CoreException { + Display display = AdtPlugin.getDisplay(); + display.asyncExec(new Runnable() { + public void run() { + openFile(file); + mEditor.getGraphicalEditor().refreshProjectResources(); + // Save file to trigger include finder scanning (as well as making + // the + // actual show-include feature work since it relies on reading + // files from + // disk, not a live buffer) + IWorkbenchPage page = mEditor.getEditorSite().getPage(); + page.saveEditor(mEditor, false); + } + }); + + // Not undoable: just return null instead of an undo-change. + return null; + } + }; + } + + private String computeNamespaceDeclarations() { + String androidNsPrefix = null; + String namespaceDeclarations = null; + + StringBuilder sb = new StringBuilder(); + List<Attr> attributeNodes = findNamespaceAttributes(); + for (Node attributeNode : attributeNodes) { + String prefix = attributeNode.getPrefix(); + if (XMLNS.equals(prefix)) { + sb.append(' '); + String name = attributeNode.getNodeName(); + sb.append(name); + sb.append('=').append('"'); + + String value = attributeNode.getNodeValue(); + if (value.equals(ANDROID_URI)) { + androidNsPrefix = name; + if (androidNsPrefix.startsWith(XMLNS_COLON)) { + androidNsPrefix = androidNsPrefix.substring(XMLNS_COLON.length()); + } + } + sb.append(DomUtilities.toXmlAttributeValue(value)); + sb.append('"'); + } + } + namespaceDeclarations = sb.toString(); + + if (androidNsPrefix == null) { + androidNsPrefix = ANDROID_NS_NAME; + } + + if (namespaceDeclarations.length() == 0) { + sb.setLength(0); + sb.append(' '); + sb.append(XMLNS_COLON); + sb.append(androidNsPrefix); + sb.append('=').append('"'); + sb.append(ANDROID_URI); + sb.append('"'); + namespaceDeclarations = sb.toString(); + } + + return namespaceDeclarations; + } + + /** Returns the id to be used for the include tag itself (may be null) */ + private String getReferenceId() { + String rootId = getRootId(); + if (rootId != null) { + return rootId + "_ref"; + } + + return null; + } + + /** + * Compute the actual {@code <include>} string to be inserted in place of the old + * selection + */ + private static String computeIncludeString(Element primaryNode, String newName, + String androidNsPrefix, String referenceId) { + StringBuilder sb = new StringBuilder(); + sb.append("<include layout=\"@layout/"); //$NON-NLS-1$ + sb.append(newName); + sb.append('"'); + sb.append(' '); + + // Create new id for the include itself + if (referenceId != null) { + sb.append(androidNsPrefix); + sb.append(':'); + sb.append(ATTR_ID); + sb.append('=').append('"'); + sb.append(referenceId); + sb.append('"').append(' '); + } + + // Add id string, unless it's a <merge>, since we may need to adjust any layout + // references to apply to the <include> tag instead + + // I should move all the layout_ attributes as well + // I also need to duplicate and modify the id and then replace + // everything else in the file with this new id... + + // HACK: see issue 13494: We must duplicate the width/height attributes on the + // <include> statement for designtime rendering only + String width = null; + String height = null; + if (primaryNode == null) { + // Multiple selection - in that case we will be creating an outer <merge> + // so we need to set our own width/height on it + width = height = VALUE_WRAP_CONTENT; + } else { + if (!primaryNode.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH)) { + width = VALUE_WRAP_CONTENT; + } else { + width = primaryNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH); + } + if (!primaryNode.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT)) { + height = VALUE_WRAP_CONTENT; + } else { + height = primaryNode.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT); + } + } + if (width != null) { + sb.append(' '); + sb.append(androidNsPrefix); + sb.append(':'); + sb.append(ATTR_LAYOUT_WIDTH); + sb.append('=').append('"'); + sb.append(DomUtilities.toXmlAttributeValue(width)); + sb.append('"'); + } + if (height != null) { + sb.append(' '); + sb.append(androidNsPrefix); + sb.append(':'); + sb.append(ATTR_LAYOUT_HEIGHT); + sb.append('=').append('"'); + sb.append(DomUtilities.toXmlAttributeValue(height)); + sb.append('"'); + } + + // Duplicate all the other layout attributes as well + if (primaryNode != null) { + NamedNodeMap attributes = primaryNode.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node attr = attributes.item(i); + String name = attr.getLocalName(); + if (name.startsWith(ATTR_LAYOUT_PREFIX) + && ANDROID_URI.equals(attr.getNamespaceURI())) { + if (name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) { + // Already handled + continue; + } + + sb.append(' '); + sb.append(androidNsPrefix); + sb.append(':'); + sb.append(name); + sb.append('=').append('"'); + sb.append(DomUtilities.toXmlAttributeValue(attr.getNodeValue())); + sb.append('"'); + } + } + } + + sb.append("/>"); + return sb.toString(); + } + + /** Return the text in the document in the range start to end */ + private String getExtractedText() { + String xml = getText(mSelectionStart, mSelectionEnd); + Element primaryNode = getPrimaryElement(); + xml = stripTopLayoutAttributes(primaryNode, mSelectionStart, xml); + xml = dedent(xml); + + // Wrap siblings in <merge>? + if (primaryNode == null) { + StringBuilder sb = new StringBuilder(); + sb.append("<merge>\n"); //$NON-NLS-1$ + // indent an extra level + for (String line : xml.split("\n")) { //$NON-NLS-1$ + sb.append(" "); //$NON-NLS-1$ + sb.append(line).append('\n'); + } + sb.append("</merge>\n"); //$NON-NLS-1$ + xml = sb.toString(); + } + + return xml; + } + + @Override + VisualRefactoringWizard createWizard() { + return new ExtractIncludeWizard(this, mEditor); + } + + public static class Descriptor extends VisualRefactoringDescriptor { + public Descriptor(String project, String description, String comment, + Map<String, String> arguments) { + super("com.android.ide.eclipse.adt.refactoring.extract.include", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new ExtractIncludeRefactoring(args); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeWizard.java new file mode 100644 index 0000000..102390f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeWizard.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.resources.ResourceType; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +class ExtractIncludeWizard extends VisualRefactoringWizard { + public ExtractIncludeWizard(ExtractIncludeRefactoring ref, LayoutEditor editor) { + super(ref, editor); + setDefaultPageTitle(ref.getName()); + } + + @Override + protected void addUserInputPages() { + ExtractIncludeRefactoring ref = (ExtractIncludeRefactoring) getRefactoring(); + String initialName = ref.getInitialName(); + IFile sourceFile = ref.getSourceFile(); + addPage(new InputPage(mEditor.getProject(), sourceFile, initialName)); + } + + /** Wizard page which inputs parameters for the {@link ExtractIncludeRefactoring} operation */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private final IFile mSourceFile; + private final String mSuggestedName; + private Text mNameText; + private Button mReplaceAllOccurrences; + + public InputPage(IProject project, IFile sourceFile, String suggestedName) { + super("ExtractIncludeInputPage"); + mProject = project; + mSourceFile = sourceFile; + mSuggestedName = suggestedName; + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label nameLabel = new Label(composite, SWT.NONE); + nameLabel.setText("New Layout Name:"); + nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + + mNameText = new Text(composite, SWT.BORDER); + mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mNameText.addModifyListener(mModifyValidateListener); + + mReplaceAllOccurrences = new Button(composite, SWT.CHECK); + mReplaceAllOccurrences.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, + false, false, 2, 1)); + mReplaceAllOccurrences.setText( + "Replace occurrences in all layouts with include to new layout"); + mReplaceAllOccurrences.setEnabled(true); + mReplaceAllOccurrences.setSelection(true); + mReplaceAllOccurrences.addSelectionListener(mSelectionValidateListener); + + // Initialize UI: + if (mSuggestedName != null) { + mNameText.setText(mSuggestedName); + } + + setControl(composite); + validatePage(); + } + + @Override + protected boolean validatePage() { + boolean ok = true; + + String text = mNameText.getText().trim(); + + if (text.length() == 0) { + setErrorMessage("Provide a name for the new layout"); + ok = false; + } else { + ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, + ResourceType.LAYOUT); + String message = validator.isValid(text); + if (message != null) { + setErrorMessage(message); + ok = false; + } + } + + if (ok) { + setErrorMessage(null); + + // Record state + ExtractIncludeRefactoring refactoring = + (ExtractIncludeRefactoring) getRefactoring(); + refactoring.setLayoutName(text); + refactoring.setReplaceOccurrences(mReplaceAllOccurrences.getSelection()); + } + + setPageComplete(ok); + return ok; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java new file mode 100644 index 0000000..4eef6b8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Extract Style" menu item is invoked. + */ +public class ExtractStyleAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + ExtractStyleRefactoring ref = new ExtractStyleRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new ExtractStyleWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Extract Style...", editor, ExtractStyleAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java new file mode 100644 index 0000000..95fbdbc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleContribution.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class ExtractStyleContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new ExtractStyleRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof ExtractStyleRefactoring.Descriptor) { + return ((ExtractStyleRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java new file mode 100644 index 0000000..d8dfe86 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.AndroidConstants.FD_RES_VALUES; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ON_CLICK; +import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC; +import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; +import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ITEM_TAG; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.PARENT_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ROOT_ELEMENT; +import static com.android.sdklib.SdkConstants.FD_RESOURCES; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Extracts the selection and writes it out as a separate layout file, then adds an + * include to that new layout file. Interactively asks the user for a new name for the + * layout. + * <p> + * Remaining work to do / Possible enhancements: + * <ul> + * <li>Optionally look in other files in the project and attempt to set style attributes + * in other cases where the style attributes match? + * <li>If the elements we are extracting from already contain a style attribute, set that + * style as the parent style of the current style? + * <li>Add a parent-style picker to the wizard (initialized with the above if applicable) + * <li>Pick up indentation settings from the XML module + * <li>Integrate with themes somehow -- make an option to have the extracted style go into + * the theme instead + * </ul> + */ +@SuppressWarnings("restriction") // XML model +public class ExtractStyleRefactoring extends VisualRefactoring { + private static final String KEY_NAME = "name"; //$NON-NLS-1$ + private static final String KEY_REMOVE_EXTRACTED = "removeextracted"; //$NON-NLS-1$ + private static final String KEY_REMOVE_ALL = "removeall"; //$NON-NLS-1$ + private static final String KEY_APPLY_STYLE = "applystyle"; //$NON-NLS-1$ + private static final String KEY_PARENT = "parent"; //$NON-NLS-1$ + private String mStyleName; + /** The name of the file in res/values/ that the style will be added to. Normally + * res/values/styles.xml - but unit tests pick other names */ + private String mStyleFileName = "styles.xml"; + /** Set a style reference on the extracted elements? */ + private boolean mApplyStyle; + /** Remove the attributes that were extracted? */ + private boolean mRemoveExtracted; + /** List of attributes chosen by the user to be extracted */ + private List<Attr> mChosenAttributes = new ArrayList<Attr>(); + /** Remove all attributes that match the extracted attributes names, regardless of value */ + private boolean mRemoveAll; + /** The parent style to extend */ + private String mParent; + /** The full list of available attributes in the refactoring */ + private Map<String, List<Attr>> mAvailableAttributes; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + ExtractStyleRefactoring(Map<String, String> arguments) { + super(arguments); + mStyleName = arguments.get(KEY_NAME); + mRemoveExtracted = Boolean.parseBoolean(arguments.get(KEY_REMOVE_EXTRACTED)); + mRemoveAll = Boolean.parseBoolean(arguments.get(KEY_REMOVE_ALL)); + mApplyStyle = Boolean.parseBoolean(arguments.get(KEY_APPLY_STYLE)); + mParent = arguments.get(KEY_PARENT); + } + + public ExtractStyleRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @VisibleForTesting + ExtractStyleRefactoring(List<Element> selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 6); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to extract"); + return status; + } + + // This also ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to extract"); + return status; + } + + pm.worked(1); + return status; + + } finally { + pm.done(); + } + } + + @Override + protected VisualRefactoringDescriptor createDescriptor() { + String comment = getName(); + return new Descriptor( + mProject.getName(), //project + comment, //description + comment, //comment + createArgumentMap()); + } + + @Override + protected Map<String, String> createArgumentMap() { + Map<String, String> args = super.createArgumentMap(); + args.put(KEY_NAME, mStyleName); + args.put(KEY_REMOVE_EXTRACTED, Boolean.toString(mRemoveExtracted)); + args.put(KEY_REMOVE_ALL, Boolean.toString(mRemoveAll)); + args.put(KEY_APPLY_STYLE, Boolean.toString(mApplyStyle)); + args.put(KEY_PARENT, mParent); + + return args; + } + + @Override + public String getName() { + return "Extract Style"; + } + + void setStyleName(String styleName) { + mStyleName = styleName; + } + + void setStyleFileName(String styleFileName) { + mStyleFileName = styleFileName; + } + + void setChosenAttributes(List<Attr> attributes) { + mChosenAttributes = attributes; + } + + void setRemoveExtracted(boolean removeExtracted) { + mRemoveExtracted = removeExtracted; + } + + void setApplyStyle(boolean applyStyle) { + mApplyStyle = applyStyle; + } + + void setRemoveAll(boolean removeAll) { + mRemoveAll = removeAll; + } + + void setParent(String parent) { + mParent = parent; + } + + // ---- Actual implementation of Extract Style modification computation ---- + + /** + * Returns two items: a map from attribute name to a list of attribute nodes of that + * name, and a subset of these attributes that fall within the text selection + * (used to drive initial selection in the wizard) + */ + Pair<Map<String, List<Attr>>, Set<Attr>> getAvailableAttributes() { + mAvailableAttributes = new TreeMap<String, List<Attr>>(); + Set<Attr> withinSelection = new HashSet<Attr>(); + for (Element element : getElements()) { + IndexedRegion elementRegion = getRegion(element); + boolean allIncluded = + (mOriginalSelectionStart <= elementRegion.getStartOffset() && + mOriginalSelectionEnd >= elementRegion.getEndOffset()); + + NamedNodeMap attributeMap = element.getAttributes(); + for (int i = 0, n = attributeMap.getLength(); i < n; i++) { + Attr attribute = (Attr) attributeMap.item(i); + + String name = attribute.getLocalName(); + if (name == null || name.equals(ATTR_ID) || name.startsWith(ATTR_STYLE) + || (name.startsWith(ATTR_LAYOUT_PREFIX) && + !name.startsWith(ATTR_LAYOUT_MARGIN)) + || name.equals(ATTR_TEXT) + || name.equals(ATTR_HINT) + || name.equals(ATTR_SRC) + || name.equals(ATTR_ON_CLICK)) { + // Don't offer to extract attributes that don't make sense in + // styles (like "id" or "style"), or attributes that the user + // probably does not want to define in styles (like layout + // attributes such as layout_width, or the label of a button etc). + // This makes the options offered listed in the wizard simpler. + // In special cases where the user *does* want to set one of these + // attributes, they can always do it manually so optimize for + // the common case here. + continue; + } + + // Skip attributes that are in a namespace other than the Android one + String namespace = attribute.getNamespaceURI(); + if (namespace != null && !ANDROID_URI.equals(namespace)) { + continue; + } + + if (!allIncluded) { + IndexedRegion region = getRegion(attribute); + boolean attributeIncluded = mOriginalSelectionStart < region.getEndOffset() && + mOriginalSelectionEnd >= region.getStartOffset(); + if (attributeIncluded) { + withinSelection.add(attribute); + } + } else { + withinSelection.add(attribute); + } + + List<Attr> list = mAvailableAttributes.get(name); + if (list == null) { + list = new ArrayList<Attr>(); + mAvailableAttributes.put(name, list); + } + list.add(attribute); + } + } + + return Pair.of(mAvailableAttributes, withinSelection); + } + + IFile getStyleFile(IProject project) { + return project.getFile(new Path(FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP + + mStyleFileName)); + } + + @Override + protected List<Change> computeChanges(IProgressMonitor monitor) { + List<Change> changes = new ArrayList<Change>(); + if (mChosenAttributes.size() == 0) { + return changes; + } + + IFile file = getStyleFile(mEditor.getProject()); + boolean createFile = !file.exists(); + int insertAtIndex; + String initialIndent = null; + if (!createFile) { + Pair<Integer, String> context = computeInsertContext(file); + insertAtIndex = context.getFirst(); + initialIndent = context.getSecond(); + } else { + insertAtIndex = 0; + } + + TextFileChange addFile = new TextFileChange("Create new separate style declaration", file); + addFile.setTextType(EXT_XML); + changes.add(addFile); + String styleString = computeStyleDeclaration(createFile, initialIndent); + addFile.setEdit(new InsertEdit(insertAtIndex, styleString)); + + // Remove extracted attributes? + MultiTextEdit rootEdit = new MultiTextEdit(); + if (mRemoveExtracted || mRemoveAll) { + for (Attr attribute : mChosenAttributes) { + List<Attr> list = mAvailableAttributes.get(attribute.getLocalName()); + for (Attr attr : list) { + if (mRemoveAll || attr.getValue().equals(attribute.getValue())) { + removeAttribute(rootEdit, attr); + } + } + } + } + + // Set the style attribute? + if (mApplyStyle) { + for (Element element : getElements()) { + String value = ResourceResolver.PREFIX_RESOURCE_REF + + ResourceResolver.REFERENCE_STYLE + mStyleName; + setAttribute(rootEdit, element, null, null, ATTR_STYLE, value); + } + } + + if (rootEdit.hasChildren()) { + IFile sourceFile = mEditor.getInputFile(); + TextFileChange change = new TextFileChange(sourceFile.getName(), sourceFile); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + changes.add(change); + } + + return changes; + } + + private String computeStyleDeclaration(boolean createFile, String initialIndent) { + StringBuilder sb = new StringBuilder(); + if (createFile) { + sb.append(NewXmlFileWizard.XML_HEADER_LINE); + sb.append('<').append(ROOT_ELEMENT).append(' '); + sb.append(XMLNS_COLON).append(ANDROID_NS_NAME).append('=').append('"'); + sb.append(ANDROID_URI); + sb.append('"').append('>').append('\n'); + } + + // Indent. Use the existing indent found for previous <style> elements in + // the resource file - but if that indent was 0 (e.g. <style> elements are + // at the left margin) only use it to indent the style elements and use a real + // nonzero indent for its children. + String indent = " "; //$NON-NLS-1$ + if (initialIndent == null) { + initialIndent = indent; + } else if (initialIndent.length() > 0) { + indent = initialIndent; + } + sb.append(initialIndent); + String styleTag = "style"; //$NON-NLS-1$ // TODO - use constant in parallel changeset + sb.append('<').append(styleTag).append(' ').append(NAME_ATTR).append('=').append('"'); + sb.append(mStyleName); + sb.append('"'); + if (mParent != null) { + sb.append(' ').append(PARENT_ATTR).append('=').append('"'); + sb.append(mParent); + sb.append('"'); + } + sb.append('>').append('\n'); + + for (Attr attribute : mChosenAttributes) { + sb.append(initialIndent).append(indent); + sb.append('<').append(ITEM_TAG).append(' ').append(NAME_ATTR).append('=').append('"'); + // We've already enforced that regardless of prefix, only attributes with + // an Android namespace can be in the set of chosen attributes. Rewrite the + // prefix to android here. + if (attribute.getPrefix() != null) { + sb.append(ANDROID_NS_NAME_PREFIX); + } + sb.append(attribute.getLocalName()); + sb.append('"').append('>'); + sb.append(attribute.getValue()); + sb.append('<').append('/').append(ITEM_TAG).append('>').append('\n'); + } + sb.append(initialIndent).append('<').append('/').append(styleTag).append('>').append('\n'); + + if (createFile) { + sb.append('<').append('/').append(ROOT_ELEMENT).append('>').append('\n'); + } + String styleString = sb.toString(); + return styleString; + } + + /** Computes the location in the file to insert the new style element at, as well as + * the exact indent string to use to indent the {@code <style>} element. + * @param file the styles.xml file to insert into + * @return a pair of an insert offset and an indent string + */ + private Pair<Integer, String> computeInsertContext(final IFile file) { + int insertAtIndex = -1; + // Find the insert of the final </resources> item where we will insert + // the new style elements. + String indent = null; + IModelManager modelManager = StructuredModelManager.getModelManager(); + IStructuredModel model = null; + try { + model = modelManager.getModelForRead(file); + if (model instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) model; + IDOMDocument otherDocument = domModel.getDocument(); + Element root = otherDocument.getDocumentElement(); + Node lastChild = root.getLastChild(); + if (lastChild != null) { + if (lastChild instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) lastChild; + insertAtIndex = region.getStartOffset() + region.getLength(); + } + + // Compute indent + while (lastChild != null) { + if (lastChild.getNodeType() == Node.ELEMENT_NODE) { + IStructuredDocument document = model.getStructuredDocument(); + indent = AndroidXmlEditor.getIndent(document, lastChild); + break; + } + lastChild = lastChild.getPreviousSibling(); + } + } + } + } catch (IOException e) { + AdtPlugin.log(e, null); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + + if (insertAtIndex == -1) { + String contents = AdtPlugin.readFile(file); + insertAtIndex = contents.indexOf("</" + ROOT_ELEMENT + ">"); //$NON-NLS-1$ + if (insertAtIndex == -1) { + insertAtIndex = contents.length(); + } + } + + return Pair.of(insertAtIndex, indent); + } + + @Override + VisualRefactoringWizard createWizard() { + return new ExtractStyleWizard(this, mEditor); + } + + public static class Descriptor extends VisualRefactoringDescriptor { + public Descriptor(String project, String description, String comment, + Map<String, String> arguments) { + super("com.android.ide.eclipse.adt.refactoring.extract.style", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new ExtractStyleRefactoring(args); + } + } + + /** + * Determines the parent style to be used for this refactoring + * + * @return the parent style to be used for this refactoring + */ + public String getParentStyle() { + Set<String> styles = new HashSet<String>(); + for (Element element : getElements()) { + // Includes "" for elements not setting the style + styles.add(element.getAttribute(ATTR_STYLE)); + } + + if (styles.size() > 1) { + // The elements differ in what style attributes they are set to + return null; + } + + String style = styles.iterator().next(); + if (style != null && style.length() > 0) { + return style; + } + + // None of the elements set the style -- see if they have the same widget types + // and if so offer to extend the theme style for that widget type + + Set<String> types = new HashSet<String>(); + for (Element element : getElements()) { + types.add(element.getTagName()); + } + + if (types.size() == 1) { + String view = DescriptorsUtils.getBasename(types.iterator().next()); + + ResourceResolver resolver = mEditor.getGraphicalEditor().getResourceResolver(); + // Look up the theme item name, which for a Button would be "buttonStyle", and so on. + String n = Character.toLowerCase(view.charAt(0)) + view.substring(1) + + "Style"; //$NON-NLS-1$ + ResourceValue value = resolver.findItemInTheme(n); + if (value != null) { + ResourceValue resolvedValue = resolver.resolveResValue(value); + String name = resolvedValue.getName(); + if (name != null) { + if (resolvedValue.isFramework()) { + return ResourceResolver.PREFIX_ANDROID + name; + } else { + return name; + } + } + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java new file mode 100644 index 0000000..7acfe98 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleWizard.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static org.eclipse.jface.viewers.StyledString.DECORATIONS_STYLER; +import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.resources.ResourceType; +import com.android.util.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.StyledCellLabelProvider; +import org.eclipse.jface.viewers.StyledString; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.w3c.dom.Attr; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +class ExtractStyleWizard extends VisualRefactoringWizard { + public ExtractStyleWizard(ExtractStyleRefactoring ref, LayoutEditor editor) { + super(ref, editor); + setDefaultPageTitle(ref.getName()); + } + + @Override + protected void addUserInputPages() { + String initialName = "styleName"; + addPage(new InputPage(mEditor.getProject(), initialName)); + } + + /** + * Wizard page which inputs parameters for the {@link ExtractStyleRefactoring} + * operation + */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private final String mSuggestedName; + private Text mNameText; + private Table mTable; + private Button mRemoveExtracted; + private Button mSetStyle; + private Button mRemoveAll; + private Button mExtend;; + private CheckboxTableViewer mCheckedView; + + private String mParentStyle; + private Set<Attr> mInSelection; + private List<Attr> mAllAttributes; + private int mElementCount; + private Map<Attr, Integer> mFrequencyCount; + private Set<Attr> mShown; + private List<Attr> mInitialChecked; + private List<Map.Entry<String, List<Attr>>> mRoot; + private Map<String, List<Attr>> mAvailableAttributes; + + public InputPage(IProject project, String suggestedName) { + super("ExtractStyleInputPage"); + mProject = project; + mSuggestedName = suggestedName; + } + + public void createControl(Composite parent) { + initialize(); + + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label nameLabel = new Label(composite, SWT.NONE); + nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + nameLabel.setText("Style Name:"); + + mNameText = new Text(composite, SWT.BORDER); + mNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mNameText.addModifyListener(mModifyValidateListener); + + mRemoveExtracted = new Button(composite, SWT.CHECK); + mRemoveExtracted.setSelection(true); + mRemoveExtracted.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mRemoveExtracted.setText("Remove extracted attributes"); + mRemoveExtracted.addSelectionListener(mSelectionValidateListener); + + mRemoveAll = new Button(composite, SWT.CHECK); + mRemoveAll.setSelection(false); + mRemoveAll.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mRemoveAll.setText("Remove all extracted attributes regardless of value"); + mRemoveAll.addSelectionListener(mSelectionValidateListener); + + boolean defaultSetStyle = false; + if (mParentStyle != null) { + mExtend = new Button(composite, SWT.CHECK); + mExtend.setSelection(true); + mExtend.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mExtend.setText(String.format("Extend %1$s", mParentStyle)); + mExtend.addSelectionListener(mSelectionValidateListener); + defaultSetStyle = true; + } + + mSetStyle = new Button(composite, SWT.CHECK); + mSetStyle.setSelection(defaultSetStyle); + mSetStyle.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1)); + mSetStyle.setText("Set style attribute on extracted elements"); + mSetStyle.addSelectionListener(mSelectionValidateListener); + + new Label(composite, SWT.NONE); + new Label(composite, SWT.NONE); + + Label tableLabel = new Label(composite, SWT.NONE); + tableLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + tableLabel.setText("Choose style attributes to extract:"); + + mCheckedView = CheckboxTableViewer.newCheckList(composite, SWT.BORDER + | SWT.FULL_SELECTION | SWT.HIDE_SELECTION); + mTable = mCheckedView.getTable(); + mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 2)); + ((GridData) mTable.getLayoutData()).heightHint = 200; + + mCheckedView.setContentProvider(new ArgumentContentProvider()); + mCheckedView.setLabelProvider(new ArgumentLabelProvider()); + mCheckedView.setInput(mRoot); + final Object[] initialSelection = mInitialChecked.toArray(); + mCheckedView.setCheckedElements(initialSelection); + + mCheckedView.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent event) { + // Try to disable other elements that conflict with this + boolean isChecked = event.getChecked(); + if (isChecked) { + Attr attribute = (Attr) event.getElement(); + List<Attr> list = mAvailableAttributes.get(attribute.getLocalName()); + for (Attr other : list) { + if (other != attribute && mShown.contains(other)) { + mCheckedView.setChecked(other, false); + } + } + } + + validatePage(); + } + }); + + // Select All / Deselect All + Composite buttonForm = new Composite(composite, SWT.NONE); + buttonForm.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); + rowLayout.marginTop = 0; + rowLayout.marginLeft = 0; + buttonForm.setLayout(rowLayout); + Button checkAllButton = new Button(buttonForm, SWT.FLAT); + checkAllButton.setText("Select All"); + checkAllButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // Select "all" (but not conflicting settings) + mCheckedView.setCheckedElements(initialSelection); + } + }); + Button uncheckAllButton = new Button(buttonForm, SWT.FLAT); + uncheckAllButton.setText("Deselect All"); + uncheckAllButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mCheckedView.setAllChecked(false); + } + }); + + // Initialize UI: + if (mSuggestedName != null) { + mNameText.setText(mSuggestedName); + } + + setControl(composite); + validatePage(); + } + + private void initialize() { + ExtractStyleRefactoring ref = (ExtractStyleRefactoring) getRefactoring(); + + mElementCount = ref.getElements().size(); + + mParentStyle = ref.getParentStyle(); + + // Set up data structures needed by the wizard -- to compute the actual + // attributes to list in the wizard (there could be multiple attributes + // of the same name (on different elements) and we only want to show one, etc.) + + Pair<Map<String, List<Attr>>, Set<Attr>> result = ref.getAvailableAttributes(); + // List of all available attributes on the selected elements + mAvailableAttributes = result.getFirst(); + // Set of attributes that overlap the text selection, or all attributes if + // wizard is invoked from GUI context + mInSelection = result.getSecond(); + + // The root data structure, which we set as the table root. The content provider + // will produce children from it. This is the entry set of a map from + // attribute name to list of attribute nodes for that attribute name. + mRoot = new ArrayList<Map.Entry<String, List<Attr>>>( + mAvailableAttributes.entrySet()); + + // Sort the items by attribute name -- the attribute name is the key + // in the entry set above. + Collections.sort(mRoot, new Comparator<Map.Entry<String, List<Attr>>>() { + public int compare(Map.Entry<String, List<Attr>> e1, + Map.Entry<String, List<Attr>> e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + // Set of attributes actually included in the list shown to the user. + // (There could be many additional "aliasing" nodes on other elements + // with the same name.) Note however that we DO show multiple attribute + // occurrences of the same attribute name: precisely one for each unique -value- + // of that attribute. + mShown = new HashSet<Attr>(); + + // The list of initially checked attributes. + mInitialChecked = new ArrayList<Attr>(); + + // All attributes. + mAllAttributes = new ArrayList<Attr>(); + + // Frequency count, from attribute to integer. Attributes that do not + // appear in the list have frequency 1, not 0. + mFrequencyCount = new HashMap<Attr, Integer>(); + + for (Map.Entry<String, List<Attr>> entry : mRoot) { + // Iterate over all attributes of the same name, and sort them + // by value. This will make it easy to list each -unique- value in the + // wizard. + List<Attr> attrList = entry.getValue(); + Collections.sort(attrList, new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + return a1.getValue().compareTo(a2.getValue()); + } + }); + + // We need to compute a couple of things: the frequency for all identical + // values (and stash them in the frequency map), and record the first + // attribute with a particular value into the list of attributes to + // be shown. + Attr prevAttr = null; + String prev = null; + List<Attr> uniqueValueAttrs = new ArrayList<Attr>(); + for (Attr attr : attrList) { + String value = attr.getValue(); + if (value.equals(prev)) { + Integer count = mFrequencyCount.get(prevAttr); + if (count == null) { + count = Integer.valueOf(2); + } else { + count = Integer.valueOf(count.intValue() + 1); + } + mFrequencyCount.put(prevAttr, count); + } else { + uniqueValueAttrs.add(attr); + prev = value; + prevAttr = attr; + } + } + + // Sort the values by frequency (and for equal frequencies, alphabetically + // by value) + Collections.sort(uniqueValueAttrs, new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + Integer f1 = mFrequencyCount.get(a1); + Integer f2 = mFrequencyCount.get(a2); + if (f1 == null) { + f1 = Integer.valueOf(1); + } + if (f2 == null) { + f2 = Integer.valueOf(1); + } + int delta = f2.intValue() - f1.intValue(); + if (delta != 0) { + return delta; + } else { + return a1.getValue().compareTo(a2.getValue()); + } + } + }); + + // Add the items in order, and select those attributes that overlap + // the selection + mAllAttributes.addAll(uniqueValueAttrs); + mShown.addAll(uniqueValueAttrs); + Attr first = uniqueValueAttrs.get(0); + if (mInSelection.contains(first)) { + mInitialChecked.add(first); + } + } + } + + @Override + protected boolean validatePage() { + boolean ok = true; + + String text = mNameText.getText().trim(); + + if (text.length() == 0) { + setErrorMessage("Provide a name for the new style"); + ok = false; + } else { + ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, + ResourceType.STYLE); + String message = validator.isValid(text); + if (message != null) { + setErrorMessage(message); + ok = false; + } + } + + Object[] checkedElements = mCheckedView.getCheckedElements(); + if (checkedElements.length == 0) { + setErrorMessage("Choose at least one attribute to extract"); + ok = false; + } + + if (ok) { + setErrorMessage(null); + + // Record state + ExtractStyleRefactoring refactoring = (ExtractStyleRefactoring) getRefactoring(); + refactoring.setStyleName(text); + refactoring.setRemoveExtracted(mRemoveExtracted.getSelection()); + refactoring.setRemoveAll(mRemoveAll.getSelection()); + refactoring.setApplyStyle(mSetStyle.getSelection()); + if (mExtend != null && mExtend.getSelection()) { + refactoring.setParent(mParentStyle); + } + List<Attr> attributes = new ArrayList<Attr>(); + for (Object o : checkedElements) { + attributes.add((Attr) o); + } + refactoring.setChosenAttributes(attributes); + } + + setPageComplete(ok); + return ok; + } + + private class ArgumentLabelProvider extends StyledCellLabelProvider { + public ArgumentLabelProvider() { + } + + @Override + public void update(ViewerCell cell) { + Object element = cell.getElement(); + Attr attribute = (Attr) element; + + StyledString styledString = new StyledString(); + styledString.append(attribute.getLocalName()); + styledString.append(" = ", QUALIFIER_STYLER); + styledString.append(attribute.getValue()); + + if (mElementCount > 1) { + Integer f = mFrequencyCount.get(attribute); + String s = String.format(" (in %d/%d elements)", + f != null ? f.intValue(): 1, mElementCount); + styledString.append(s, DECORATIONS_STYLER); + } + cell.setText(styledString.toString()); + cell.setStyleRanges(styledString.getStyleRanges()); + super.update(cell); + } + } + + private class ArgumentContentProvider implements IStructuredContentProvider { + public ArgumentContentProvider() { + } + + public Object[] getElements(Object inputElement) { + if (inputElement == mRoot) { + return mAllAttributes.toArray(); + } + + return new Object[0]; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java new file mode 100644 index 0000000..4baad1d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; +import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; +import org.eclipse.jface.text.quickassist.IQuickAssistProcessor; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; +import org.eclipse.wst.sse.ui.StructuredTextEditor; +import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; + +/** + * QuickAssistProcessor which helps invoke refactoring operations on text elements. + */ +@SuppressWarnings("restriction") // XML model +public class RefactoringAssistant implements IQuickAssistProcessor { + + public RefactoringAssistant() { + } + + public boolean canAssist(IQuickAssistInvocationContext invocationContext) { + return true; + } + + public boolean canFix(Annotation annotation) { + return true; + } + + public ICompletionProposal[] computeQuickAssistProposals( + IQuickAssistInvocationContext invocationContext) { + + ISourceViewer sourceViewer = invocationContext.getSourceViewer(); + AndroidXmlEditor xmlEditor = AndroidXmlEditor.getAndroidXmlEditor(sourceViewer); + if (xmlEditor == null) { + return null; + } + + IFile file = xmlEditor.getInputFile(); + int offset = invocationContext.getOffset(); + + // Ensure that we are over a tag name (for element-based refactoring + // operations) or a value (for the extract include refactoring) + + boolean isValue = false; + boolean isTagName = false; + boolean isAttributeName = false; + IStructuredModel model = null; + try { + model = xmlEditor.getModelForRead(); + IStructuredDocument doc = model.getStructuredDocument(); + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); + ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); + if (subRegion != null) { + String type = subRegion.getType(); + if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE)) { + String value = region.getText(subRegion); + // Only extract values that aren't already resources + // (and value includes leading ' or ") + if (!value.startsWith("'@") && !value.startsWith("\"@")) { //$NON-NLS-1$ //$NON-NLS-2$ + isValue = true; + } + } else if (type.equals(DOMRegionContext.XML_TAG_NAME) + || type.equals(DOMRegionContext.XML_TAG_OPEN) + || type.equals(DOMRegionContext.XML_TAG_CLOSE)) { + isTagName = true; + } else if (type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) + || type.equals(DOMRegionContext.XML_TAG_ATTRIBUTE_EQUALS)) { + isAttributeName = true; + } + } + } finally { + if (model != null) { + model.releaseFromRead(); + } + } + + if (isValue || isTagName || isAttributeName) { + StructuredTextEditor structuredEditor = xmlEditor.getStructuredTextEditor(); + ISelectionProvider provider = structuredEditor.getSelectionProvider(); + ISelection selection = provider.getSelection(); + if (selection instanceof ITextSelection) { + ITextSelection textSelection = (ITextSelection) selection; + + // These operations currently do not work on ranges + if (textSelection.getLength() > 0) { + // ...except for Extract Style where the actual attributes overlapping + // the selection is going to be the set of eligible attributes + if (isAttributeName && xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; + return new ICompletionProposal[] { + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, textSelection, null)) + }; + } + return null; + } + + if (isAttributeName && xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; + return new ICompletionProposal[] { + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, textSelection, null)), + }; + } else if (isValue) { + if (xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; + return new ICompletionProposal[] { + new RefactoringProposal(xmlEditor, + new ExtractStringRefactoring(file, xmlEditor, + textSelection)), + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, + textSelection, null)), + }; + } else { + return new ICompletionProposal[] { + new RefactoringProposal(xmlEditor, + new ExtractStringRefactoring(file, xmlEditor, textSelection)) + }; + } + } else if (xmlEditor instanceof LayoutEditor) { + LayoutEditor editor = (LayoutEditor) xmlEditor; + return new ICompletionProposal[] { + new RefactoringProposal(editor, + new WrapInRefactoring(file, editor, textSelection, null)), + new RefactoringProposal(editor, + new ChangeViewRefactoring(file, editor, textSelection, null)), + new RefactoringProposal(editor, + new ChangeLayoutRefactoring(file, editor, textSelection, null)), + new RefactoringProposal(editor, + new ExtractStyleRefactoring(file, editor, textSelection, null)), + new RefactoringProposal(editor, + new ExtractIncludeRefactoring(file, editor, textSelection, null)), + }; + } + } + } + return null; + } + + public String getErrorMessage() { + return null; + } + + private static class RefactoringProposal + implements ICompletionProposal { + private final AndroidXmlEditor mEditor; + private final Refactoring mRefactoring; + + RefactoringProposal(AndroidXmlEditor editor, Refactoring refactoring) { + super(); + mEditor = editor; + mRefactoring = refactoring; + } + + public void apply(IDocument document) { + RefactoringWizard wizard = null; + if (mRefactoring instanceof VisualRefactoring) { + wizard = ((VisualRefactoring) mRefactoring).createWizard(); + } else if (mRefactoring instanceof ExtractStringRefactoring) { + wizard = new ExtractStringWizard((ExtractStringRefactoring) mRefactoring, + mEditor.getProject()); + } else { + throw new IllegalArgumentException(); + } + + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + op.run(window.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + } + } + + public String getAdditionalProposalInfo() { + return "Initiates the given refactoring operation"; + } + + public IContextInformation getContextInformation() { + return null; + } + + public String getDisplayString() { + return mRefactoring.getName(); + } + + public Image getImage() { + return AdtPlugin.getAndroidLogo(); + } + + public Point getSelection(IDocument document) { + return null; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java new file mode 100644 index 0000000..193c3c0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java @@ -0,0 +1,1668 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_BACKGROUND; +import static com.android.ide.common.layout.LayoutConstants.ATTR_BASELINE_ALIGNED; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ABOVE; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BOTTOM; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_LEFT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_RIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_TOP; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_BELOW; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_GRAVITY; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_LEFT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_TOP; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_BOTTOM; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_CENTER; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_CENTER_HORIZONTAL; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_CENTER_VERTICAL; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL_HORIZONTAL; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_FILL_VERTICAL; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_LEFT; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_RIGHT; +import static com.android.ide.common.layout.LayoutConstants.GRAVITY_VALUE_TOP; +import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.RELATIVE_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_FALSE; +import static com.android.ide.common.layout.LayoutConstants.VALUE_N_DP; +import static com.android.ide.common.layout.LayoutConstants.VALUE_TRUE; +import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL; +import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.util.Pair; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.text.edits.MultiTextEdit; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Helper class which performs the bulk of the layout conversion to relative layout + * <p> + * Future enhancements: + * <ul> + * <li>Render the layout at multiple screen sizes and analyze how the widgets move and + * stretch and use that to add in additional constraints + * <li> Adapt the LinearLayout analysis code to work with TableLayouts and TableRows as well + * (just need to tweak the "isVertical" interpretation to account for the different defaults, + * and perhaps do something about column size properties. + * <li> We need to take into account existing margins and clear/update them + * </ul> + */ +class RelativeLayoutConversionHelper { + private final MultiTextEdit mRootEdit; + private final boolean mFlatten; + private final Element mLayout; + private final ChangeLayoutRefactoring mRefactoring; + private final CanvasViewInfo mRootView; + + RelativeLayoutConversionHelper(ChangeLayoutRefactoring refactoring, + Element layout, boolean flatten, MultiTextEdit rootEdit, CanvasViewInfo rootView) { + mRefactoring = refactoring; + mLayout = layout; + mFlatten = flatten; + mRootEdit = rootEdit; + mRootView = rootView; + } + + /** Performs conversion from any layout to a RelativeLayout */ + public void convertToRelative() { + // Locate the view for the layout + CanvasViewInfo layoutView = findViewForElement(mRootView, mLayout); + if (layoutView == null || layoutView.getChildren().size() == 0) { + // No children. THAT was an easy conversion! + return; + } + + // Study the layout and get information about how to place individual elements + List<View> views = analyzeLayout(layoutView); + + // Create/update relative layout constraints + createAttachments(views); + } + + /** + * Analyzes the given view hierarchy and produces a list of {@link View} objects which + * contain placement information for each element + */ + private List<View> analyzeLayout(CanvasViewInfo layoutView) { + EdgeList edgeList = new EdgeList(layoutView); + deleteRemovedElements(edgeList.getDeletedElements()); + + List<Integer> columnOffsets = edgeList.getColumnOffsets(); + List<Integer> rowOffsets = edgeList.getRowOffsets(); + + // Compute x/y offsets for each row/column index + int[] left = new int[columnOffsets.size()]; + int[] top = new int[rowOffsets.size()]; + + Map<Integer, Integer> xToCol = new HashMap<Integer, Integer>(); + int columnIndex = 0; + for (Integer offset : columnOffsets) { + left[columnIndex] = offset; + xToCol.put(offset, columnIndex++); + } + Map<Integer, Integer> yToRow = new HashMap<Integer, Integer>(); + int rowIndex = 0; + for (Integer offset : rowOffsets) { + top[rowIndex] = offset; + yToRow.put(offset, rowIndex++); + } + + // Create a complete list of view objects + List<View> views = createViews(edgeList, columnOffsets); + initializeSpans(edgeList, columnOffsets, rowOffsets, xToCol, yToRow); + + // Sanity check + for (View view : views) { + assert view.getLeftEdge() == left[view.mCol]; + assert view.getTopEdge() == top[view.mRow]; + assert view.getRightEdge() == left[view.mCol+view.mColSpan]; + assert view.getBottomEdge() == top[view.mRow+view.mRowSpan]; + } + + // Ensure that every view has a proper id such that it can be referred to + // with a constraint + initializeIds(edgeList, views); + + // Attempt to lay the views out in a grid with constraints (though not that widgets + // can overlap as well) + Grid grid = new Grid(views, left, top); + computeKnownConstraints(views, edgeList); + computeHorizontalConstraints(grid); + computeVerticalConstraints(grid); + + return views; + } + + /** Produces a list of {@link View} objects from an {@link EdgeList} */ + private List<View> createViews(EdgeList edgeList, List<Integer> columnOffsets) { + List<View> views = new ArrayList<View>(); + for (Integer offset : columnOffsets) { + List<View> leftEdgeViews = edgeList.getLeftEdgeViews(offset); + if (leftEdgeViews == null) { + // must have been a right edge + continue; + } + for (View view : leftEdgeViews) { + views.add(view); + } + } + return views; + } + + /** Removes any elements targeted for deletion */ + private void deleteRemovedElements(List<Element> delete) { + if (mFlatten && delete.size() > 0) { + for (Element element : delete) { + mRefactoring.removeElementTags(mRootEdit, element, delete); + } + } + } + + /** Ensures that every element has an id such that it can be referenced from a constraint */ + private void initializeIds(EdgeList edgeList, List<View> views) { + // Ensure that all views have a valid id + for (View view : views) { + String id = mRefactoring.ensureHasId(mRootEdit, view.mElement, null); + edgeList.setIdAttributeValue(view, id); + } + } + + /** + * Initializes the column and row indices, as well as any column span and row span + * values + */ + private void initializeSpans(EdgeList edgeList, List<Integer> columnOffsets, + List<Integer> rowOffsets, Map<Integer, Integer> xToCol, Map<Integer, Integer> yToRow) { + // Now initialize table view row, column and spans + for (Integer offset : columnOffsets) { + List<View> leftEdgeViews = edgeList.getLeftEdgeViews(offset); + if (leftEdgeViews == null) { + // must have been a right edge + continue; + } + for (View view : leftEdgeViews) { + Integer col = xToCol.get(view.getLeftEdge()); + assert col != null; + Integer end = xToCol.get(view.getRightEdge()); + assert end != null; + + view.mCol = col; + view.mColSpan = end - col; + } + } + + for (Integer offset : rowOffsets) { + List<View> topEdgeViews = edgeList.getTopEdgeViews(offset); + if (topEdgeViews == null) { + // must have been a bottom edge + continue; + } + for (View view : topEdgeViews) { + Integer row = yToRow.get(view.getTopEdge()); + assert row != null; + Integer end = yToRow.get(view.getBottomEdge()); + assert end != null; + + view.mRow = row; + view.mRowSpan = end - row; + } + } + } + + /** + * Creates refactoring edits which adds or updates constraints for the given list of + * views + */ + private void createAttachments(List<View> views) { + // Make the attachments + String namespace = mRefactoring.getAndroidNamespacePrefix(); + for (View view : views) { + for (Pair<String, String> constraint : view.getHorizConstraints()) { + mRefactoring.setAttribute(mRootEdit, view.mElement, ANDROID_URI, + namespace, constraint.getFirst(), constraint.getSecond()); + } + for (Pair<String, String> constraint : view.getVerticalConstraints()) { + mRefactoring.setAttribute(mRootEdit, view.mElement, ANDROID_URI, + namespace, constraint.getFirst(), constraint.getSecond()); + } + } + } + + /** + * Analyzes the existing layouts and layout parameter objects in the document to infer + * constraints for layout types that we know about - such as LinearLayout baseline + * alignment, weights, gravity, etc. + */ + private void computeKnownConstraints(List<View> views, EdgeList edgeList) { + // List of parent layout elements we've already processed. We iterate through all + // the -children-, and we ask each for its element parent (which won't have a view) + // and we look at the parent's layout attributes and its children layout constraints, + // and then we stash away constraints that we can infer. This means that we will + // encounter the same parent for every sibling, so that's why there's a map to + // prevent duplicate work. + Set<Node> seen = new HashSet<Node>(); + + for (View view : views) { + Element element = view.getElement(); + Node parent = element.getParentNode(); + if (seen.contains(parent)) { + continue; + } + seen.add(parent); + + if (parent.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + Element layout = (Element) parent; + String layoutName = layout.getTagName(); + + if (LINEAR_LAYOUT.equals(layoutName)) { + analyzeLinearLayout(edgeList, layout); + } else if (RELATIVE_LAYOUT.equals(layoutName)) { + analyzeRelativeLayout(edgeList, layout); + } else { + // Some other layout -- add more conditional handling here + // for framelayout, tables, etc. + } + } + } + + private static final int GRAVITY_LEFT = 1 << 0; + private static final int GRAVITY_RIGHT = 1<< 1; + private static final int GRAVITY_CENTER_HORIZ = 1 << 2; + private static final int GRAVITY_FILL_HORIZ = 1 << 3; + private static final int GRAVITY_CENTER_VERT = 1 << 4; + private static final int GRAVITY_FILL_VERT = 1 << 5; + private static final int GRAVITY_TOP = 1 << 6; + private static final int GRAVITY_BOTTOM = 1 << 7; + private static final int GRAVITY_HORIZ_MASK = GRAVITY_CENTER_HORIZ | GRAVITY_FILL_HORIZ + | GRAVITY_LEFT | GRAVITY_RIGHT; + private static final int GRAVITY_VERT_MASK = GRAVITY_CENTER_VERT | GRAVITY_FILL_VERT + | GRAVITY_TOP | GRAVITY_BOTTOM; + + /** Returns the gravity of the given element */ + private static int getGravity(Element element) { + int gravity = GRAVITY_LEFT | GRAVITY_TOP; + String gravityString = element.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY); + if (gravityString != null && gravityString.length() > 0) { + String[] anchors = gravityString.split("\\|"); //$NON-NLS-1$ + for (String anchor : anchors) { + if (GRAVITY_VALUE_CENTER.equals(anchor)) { + gravity = GRAVITY_CENTER_HORIZ | GRAVITY_CENTER_VERT; + } else if (GRAVITY_VALUE_FILL.equals(anchor)) { + gravity = GRAVITY_FILL_HORIZ | GRAVITY_FILL_VERT; + } else if (GRAVITY_VALUE_CENTER_VERTICAL.equals(anchor)) { + gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_CENTER_VERT; + } else if (GRAVITY_VALUE_CENTER_HORIZONTAL.equals(anchor)) { + gravity = (gravity & GRAVITY_VERT_MASK) | GRAVITY_CENTER_HORIZ; + } else if (GRAVITY_VALUE_FILL_VERTICAL.equals(anchor)) { + gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_FILL_VERT; + } else if (GRAVITY_VALUE_FILL_HORIZONTAL.equals(anchor)) { + gravity = (gravity & GRAVITY_VERT_MASK) | GRAVITY_FILL_HORIZ; + } else if (GRAVITY_VALUE_TOP.equals(anchor)) { + gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_TOP; + } else if (GRAVITY_VALUE_BOTTOM.equals(anchor)) { + gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_BOTTOM; + } else if (GRAVITY_VALUE_LEFT.equals(anchor)) { + gravity = (gravity & GRAVITY_VERT_MASK) | GRAVITY_LEFT; + } else if (GRAVITY_VALUE_RIGHT.equals(anchor)) { + gravity = (gravity & GRAVITY_VERT_MASK) | GRAVITY_RIGHT; + } else { + // "clip" not supported + } + } + } + + return gravity; + } + + /** + * Returns the layout weight of of the given child of a LinearLayout, or 0.0 if it + * does not define a weight + */ + private float getWeight(Element linearLayoutChild) { + String weight = linearLayoutChild.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT); + if (weight != null && weight.length() > 0) { + try { + return Float.parseFloat(weight); + } catch (NumberFormatException nfe) { + AdtPlugin.log(nfe, "Invalid weight %1$s", weight); + } + } + + return 0.0f; + } + + /** + * Returns the sum of all the layout weights of the children in the given LinearLayout + * + * @param linearLayout the layout to compute the total sum for + * @return the total sum of all the layout weights in the given layout + */ + private float getWeightSum(Element linearLayout) { + float sum = 0; + for (Element child : DomUtilities.getChildren(linearLayout)) { + sum += getWeight(child); + } + + return sum; + } + + /** + * Analyzes the given LinearLayout and updates the constraints to reflect + * relationships it can infer - based on baseline alignment, gravity, order and + * weights. This method also removes "0dip" as a special width/height used in + * LinearLayouts with weight distribution. + */ + private void analyzeLinearLayout(EdgeList edgeList, Element layout) { + boolean isVertical = VALUE_VERTICAL.equals(layout.getAttributeNS(ANDROID_URI, + ATTR_ORIENTATION)); + View baselineRef = null; + if (!isVertical && + !VALUE_FALSE.equals(layout.getAttributeNS(ANDROID_URI, ATTR_BASELINE_ALIGNED))) { + // Baseline alignment. Find the tallest child and set it as the baseline reference. + int tallestHeight = 0; + View tallest = null; + for (Element child : DomUtilities.getChildren(layout)) { + View view = edgeList.getView(child); + if (view != null && view.getHeight() > tallestHeight) { + tallestHeight = view.getHeight(); + tallest = view; + } + } + if (tallest != null) { + baselineRef = tallest; + } + } + + float weightSum = getWeightSum(layout); + float cumulativeWeight = 0; + + List<Element> children = DomUtilities.getChildren(layout); + String prevId = null; + boolean isFirstChild = true; + boolean linkBackwards = true; + boolean linkForwards = false; + + for (int index = 0, childCount = children.size(); index < childCount; index++) { + Element child = children.get(index); + + View childView = edgeList.getView(child); + if (childView == null) { + // Could be a nested layout that is being removed etc + prevId = null; + isFirstChild = false; + continue; + } + + // Look at the layout_weight attributes and determine whether we should be + // attached on the bottom/right or on the top/left + if (weightSum > 0.0f) { + float weight = getWeight(child); + + // We can't emulate a LinearLayout where multiple children have positive + // weights. However, we CAN support the common scenario where a single + // child has a non-zero weight, and all children after it are pushed + // to the end and the weighted child fills the remaining space. + if (cumulativeWeight == 0 && weight > 0) { + // See if we have a bottom/right edge to attach the forwards link to + // (at the end of the forwards chains). Only if so can we link forwards. + View referenced; + if (isVertical) { + referenced = edgeList.getSharedBottomEdge(layout); + } else { + referenced = edgeList.getSharedRightEdge(layout); + } + if (referenced != null) { + linkForwards = true; + } + } else if (cumulativeWeight > 0) { + linkBackwards = false; + } + + cumulativeWeight += weight; + } + + analyzeGravity(edgeList, layout, isVertical, child, childView); + convert0dipToWrapContent(child); + + // Chain elements together in the flow direction of the linear layout + if (prevId != null) { // No constraint for first child + if (linkBackwards) { + if (isVertical) { + childView.addVerticalConstraint(ATTR_LAYOUT_BELOW, prevId); + } else { + childView.addHorizConstraint(ATTR_LAYOUT_TO_RIGHT_OF, prevId); + } + } + } else if (isFirstChild) { + assert linkBackwards; + + // First element; attach it to the parent if we can + if (isVertical) { + View referenced = edgeList.getSharedTopEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_TOP, + VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP, + referenced.getId()); + } + } + } else { + View referenced = edgeList.getSharedLeftEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_LEFT, + VALUE_TRUE); + } else { + childView.addHorizConstraint( + ATTR_LAYOUT_ALIGN_LEFT, referenced.getId()); + } + } + } + } + + if (linkForwards) { + if (index < (childCount - 1)) { + Element nextChild = children.get(index + 1); + String nextId = mRefactoring.ensureHasId(mRootEdit, nextChild, null); + if (nextId != null) { + if (isVertical) { + childView.addVerticalConstraint(ATTR_LAYOUT_ABOVE, nextId); + } else { + childView.addHorizConstraint(ATTR_LAYOUT_TO_LEFT_OF, nextId); + } + } + } else { + // Attach to right/bottom edge of the layout + if (isVertical) { + View referenced = edgeList.getSharedBottomEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, + VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM, + referenced.getId()); + } + } + } else { + View referenced = edgeList.getSharedRightEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, + VALUE_TRUE); + } else { + childView.addHorizConstraint( + ATTR_LAYOUT_ALIGN_RIGHT, referenced.getId()); + } + } + } + } + } + + if (baselineRef != null && !baselineRef.equals(childView.getId())) { + assert !isVertical; + // Only align if they share the same gravity + if ((childView.getGravity() & GRAVITY_VERT_MASK) == + (baselineRef.getGravity() & GRAVITY_VERT_MASK)) { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_BASELINE, baselineRef.getId()); + } + } + + prevId = mRefactoring.ensureHasId(mRootEdit, child, null); + isFirstChild = false; + } + } + + /** + * Checks the layout "gravity" value for the given child and updates the constraints + * to account for the gravity + */ + private int analyzeGravity(EdgeList edgeList, Element layout, boolean isVertical, + Element child, View childView) { + // Use gravity to constrain elements in the axis orthogonal to the + // direction of the layout + int gravity = childView.getGravity(); + if (isVertical) { + if ((gravity & GRAVITY_RIGHT) != 0) { + View referenced = edgeList.getSharedRightEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, + VALUE_TRUE); + } else { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_RIGHT, + referenced.getId()); + } + } + } else if ((gravity & GRAVITY_CENTER_HORIZ) != 0) { + View referenced1 = edgeList.getSharedLeftEdge(layout); + View referenced2 = edgeList.getSharedRightEdge(layout); + if (referenced1 != null && referenced2 == referenced1) { + if (isAncestor(referenced1.getElement(), child)) { + childView.addHorizConstraint(ATTR_LAYOUT_CENTER_HORIZONTAL, + VALUE_TRUE); + } + } + } else if ((gravity & GRAVITY_FILL_HORIZ) != 0) { + View referenced1 = edgeList.getSharedLeftEdge(layout); + View referenced2 = edgeList.getSharedRightEdge(layout); + if (referenced1 != null && referenced2 == referenced1) { + if (isAncestor(referenced1.getElement(), child)) { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_LEFT, + VALUE_TRUE); + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, + VALUE_TRUE); + } else { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_LEFT, + referenced1.getId()); + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_RIGHT, + referenced2.getId()); + } + } + } else if ((gravity & GRAVITY_LEFT) != 0) { + View referenced = edgeList.getSharedLeftEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_PARENT_LEFT, + VALUE_TRUE); + } else { + childView.addHorizConstraint(ATTR_LAYOUT_ALIGN_LEFT, + referenced.getId()); + } + } + } + } else { + // Handle horizontal layout: perform vertical gravity attachments + if ((gravity & GRAVITY_BOTTOM) != 0) { + View referenced = edgeList.getSharedBottomEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, + VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM, + referenced.getId()); + } + } + } else if ((gravity & GRAVITY_CENTER_VERT) != 0) { + View referenced1 = edgeList.getSharedTopEdge(layout); + View referenced2 = edgeList.getSharedBottomEdge(layout); + if (referenced1 != null && referenced2 == referenced1) { + if (isAncestor(referenced1.getElement(), child)) { + childView.addVerticalConstraint(ATTR_LAYOUT_CENTER_VERTICAL, + VALUE_TRUE); + } + } + } else if ((gravity & GRAVITY_FILL_VERT) != 0) { + View referenced1 = edgeList.getSharedTopEdge(layout); + View referenced2 = edgeList.getSharedBottomEdge(layout); + if (referenced1 != null && referenced2 == referenced1) { + if (isAncestor(referenced1.getElement(), child)) { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_TOP, + VALUE_TRUE); + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, + VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP, + referenced1.getId()); + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM, + referenced2.getId()); + } + } + } else if ((gravity & GRAVITY_TOP) != 0) { + View referenced = edgeList.getSharedTopEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_PARENT_TOP, + VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP, + referenced.getId()); + } + } + } + } + return gravity; + } + + /** Converts 0dip values in layout_width and layout_height to wrap_content instead */ + private void convert0dipToWrapContent(Element child) { + // Must convert layout_height="0dip" to layout_height="wrap_content". + // 0dip is a special trick used in linear layouts in the presence of + // weights where 0dip ensures that the height of the view is not taken + // into account when distributing the weights. However, when converted + // to RelativeLayout this will instead cause the view to actually be assigned + // 0 height. + String height = child.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT); + // 0dip, 0dp, 0px, etc + if (height != null && height.startsWith("0")) { //$NON-NLS-1$ + mRefactoring.setAttribute(mRootEdit, child, ANDROID_URI, + mRefactoring.getAndroidNamespacePrefix(), ATTR_LAYOUT_HEIGHT, + VALUE_WRAP_CONTENT); + } + String width = child.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH); + if (width != null && width.startsWith("0")) { //$NON-NLS-1$ + mRefactoring.setAttribute(mRootEdit, child, ANDROID_URI, + mRefactoring.getAndroidNamespacePrefix(), ATTR_LAYOUT_WIDTH, + VALUE_WRAP_CONTENT); + } + } + + /** + * Analyzes an embedded RelativeLayout within a layout hierarchy and updates the + * constraints in the EdgeList with those relationships which can continue in the + * outer single RelativeLayout. + */ + private void analyzeRelativeLayout(EdgeList edgeList, Element layout) { + NodeList children = layout.getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element child = (Element) node; + View childView = edgeList.getView(child); + if (childView == null) { + // Could be a nested layout that is being removed etc + continue; + } + + NamedNodeMap attributes = child.getAttributes(); + for (int j = 0, m = attributes.getLength(); j < m; j++) { + Attr attribute = (Attr) attributes.item(j); + String name = attribute.getLocalName(); + String value = attribute.getValue(); + if (name.equals(ATTR_LAYOUT_WIDTH) + || name.equals(ATTR_LAYOUT_HEIGHT)) { + // Ignore these for now + } else if (name.startsWith(ATTR_LAYOUT_PREFIX) + && ANDROID_URI.equals(attribute.getNamespaceURI())) { + // Determine if the reference is to a known edge + String id = getIdBasename(value); + if (id != null) { + View referenced = edgeList.getView(id); + if (referenced != null) { + // This is a valid reference, so preserve + // the attribute + if (name.equals(ATTR_LAYOUT_BELOW) || + name.equals(ATTR_LAYOUT_ABOVE) || + name.equals(ATTR_LAYOUT_ALIGN_TOP) || + name.equals(ATTR_LAYOUT_ALIGN_BOTTOM) || + name.equals(ATTR_LAYOUT_ALIGN_BASELINE)) { + // Vertical constraint + childView.addVerticalConstraint(name, value); + } else if (name.equals(ATTR_LAYOUT_ALIGN_LEFT) || + name.equals(ATTR_LAYOUT_TO_LEFT_OF) || + name.equals(ATTR_LAYOUT_TO_RIGHT_OF) || + name.equals(ATTR_LAYOUT_ALIGN_RIGHT)) { + // Horizontal constraint + childView.addHorizConstraint(name, value); + } else { + // We don't expect this + assert false : name; + } + } else { + // Reference to some layout that is not included here. + // TODO: See if the given layout has an edge + // that corresponds to one of our known views + // so we can adjust the constraints and keep it after all. + } + } else { + // It's a parent-relative constraint (such + // as aligning with a parent edge, or centering + // in the parent view) + boolean remove = true; + if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_LEFT)) { + View referenced = edgeList.getSharedLeftEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addHorizConstraint(name, VALUE_TRUE); + } else { + childView.addHorizConstraint( + ATTR_LAYOUT_ALIGN_LEFT, referenced.getId()); + } + remove = false; + } + } else if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_RIGHT)) { + View referenced = edgeList.getSharedRightEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addHorizConstraint(name, VALUE_TRUE); + } else { + childView.addHorizConstraint( + ATTR_LAYOUT_ALIGN_RIGHT, referenced.getId()); + } + remove = false; + } + } else if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_TOP)) { + View referenced = edgeList.getSharedTopEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addVerticalConstraint(name, VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP, + referenced.getId()); + } + remove = false; + } + } else if (name.equals(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM)) { + View referenced = edgeList.getSharedBottomEdge(layout); + if (referenced != null) { + if (isAncestor(referenced.getElement(), child)) { + childView.addVerticalConstraint(name, VALUE_TRUE); + } else { + childView.addVerticalConstraint(ATTR_LAYOUT_ALIGN_BOTTOM, + referenced.getId()); + } + remove = false; + } + } + + boolean alignWithParent = + name.equals(ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING); + if (remove && alignWithParent) { + // TODO - look for this one AFTER we have processed + // everything else, and then set constraints as necessary + // IF there are no other conflicting constraints! + } + + // Otherwise it's some kind of centering which we don't support + // yet. + + // TODO: Find a way to determine whether we have + // a corresponding edge for the parent (e.g. if + // the ViewInfo bounds match our outer parent or + // some other edge) and if so, substitute for that + // id. + // For example, if this element was centered + // horizontally in a RelativeLayout that actually + // occupies the entire width of our outer layout, + // then it can be preserved after all! + + if (remove) { + if (name.startsWith("layout_margin")) { //$NON-NLS-1$ + continue; + } + + // Remove unknown attributes? + // It's too early to do this, because we may later want + // to *set* this value and it would result in an overlapping edits + // exception. Therefore, we need to RECORD which attributes should + // be removed, which lines should have its indentation adjusted + // etc and finally process it all at the end! + //mRefactoring.removeAttribute(mRootEdit, child, + // attribute.getNamespaceURI(), name); + } + } + } + } + } + } + } + + /** + * Given {@code @id/foo} or {@code @+id/foo}, returns foo. Note that given foo it will + * return null. + */ + private static String getIdBasename(String id) { + if (id.startsWith(NEW_ID_PREFIX)) { + return id.substring(NEW_ID_PREFIX.length()); + } else if (id.startsWith(ID_PREFIX)) { + return id.substring(ID_PREFIX.length()); + } + + return null; + } + + /** Returns true if the given second argument is a descendant of the first argument */ + private static boolean isAncestor(Node ancestor, Node node) { + while (node != null) { + if (node == ancestor) { + return true; + } + node = node.getParentNode(); + } + return false; + } + + /** + * Computes horizontal constraints for the views in the grid for any remaining views + * that do not have constraints (as the result of the analysis of known layouts). This + * will look at the rendered layout coordinates and attempt to connect elements based + * on a spatial layout in the grid. + */ + private void computeHorizontalConstraints(Grid grid) { + int columns = grid.getColumns(); + + String attachLeftProperty = ATTR_LAYOUT_ALIGN_PARENT_LEFT; + String attachLeftValue = VALUE_TRUE; + int marginLeft = 0; + for (int col = 0; col < columns; col++) { + if (!grid.colContainsTopLeftCorner(col)) { + // Just accumulate margins for the next column + marginLeft += grid.getColumnWidth(col); + } else { + // Add horizontal attachments + String firstId = null; + for (View view : grid.viewsStartingInCol(col, true)) { + assert view.getId() != null; + if (firstId == null) { + firstId = view.getId(); + if (view.isConstrainedHorizontally()) { + // Nothing to do -- we already have an accurate position for + // this view + } else if (attachLeftProperty != null) { + view.addHorizConstraint(attachLeftProperty, attachLeftValue); + if (marginLeft > 0) { + view.addHorizConstraint(ATTR_LAYOUT_MARGIN_LEFT, + String.format(VALUE_N_DP, marginLeft)); + marginLeft = 0; + } + } else { + assert false; + } + } else if (!view.isConstrainedHorizontally()) { + view.addHorizConstraint(ATTR_LAYOUT_ALIGN_LEFT, firstId); + } + } + } + + // Figure out edge for the next column + View view = grid.findRightEdgeView(col); + if (view != null) { + assert view.getId() != null; + attachLeftProperty = ATTR_LAYOUT_TO_RIGHT_OF; + attachLeftValue = view.getId(); + + marginLeft = 0; + } else if (marginLeft == 0) { + marginLeft = grid.getColumnWidth(col); + } + } + } + + /** + * Performs vertical layout just like the {@link #computeHorizontalConstraints} method + * did horizontally + */ + private void computeVerticalConstraints(Grid grid) { + int rows = grid.getRows(); + + String attachTopProperty = ATTR_LAYOUT_ALIGN_PARENT_TOP; + String attachTopValue = VALUE_TRUE; + int marginTop = 0; + for (int row = 0; row < rows; row++) { + if (!grid.rowContainsTopLeftCorner(row)) { + // Just accumulate margins for the next column + marginTop += grid.getRowHeight(row); + } else { + // Add horizontal attachments + String firstId = null; + for (View view : grid.viewsStartingInRow(row, true)) { + assert view.getId() != null; + if (firstId == null) { + firstId = view.getId(); + if (view.isConstrainedVertically()) { + // Nothing to do -- we already have an accurate position for + // this view + } else if (attachTopProperty != null) { + view.addVerticalConstraint(attachTopProperty, attachTopValue); + if (marginTop > 0) { + view.addVerticalConstraint(ATTR_LAYOUT_MARGIN_TOP, + String.format(VALUE_N_DP, marginTop)); + marginTop = 0; + } + } else { + assert false; + } + } else if (!view.isConstrainedVertically()) { + view.addVerticalConstraint(ATTR_LAYOUT_ALIGN_TOP, firstId); + } + } + } + + // Figure out edge for the next row + View view = grid.findBottomEdgeView(row); + if (view != null) { + assert view.getId() != null; + attachTopProperty = ATTR_LAYOUT_BELOW; + attachTopValue = view.getId(); + marginTop = 0; + } else if (marginTop == 0) { + marginTop = grid.getRowHeight(row); + } + } + } + + /** + * Searches a view hierarchy and locates the {@link CanvasViewInfo} for the given + * {@link Element} + * + * @param info the root {@link CanvasViewInfo} to search below + * @param element the target element + * @return the {@link CanvasViewInfo} which corresponds to the given element + */ + private CanvasViewInfo findViewForElement(CanvasViewInfo info, Element element) { + if (getElement(info) == element) { + return info; + } + + for (CanvasViewInfo child : info.getChildren()) { + CanvasViewInfo result = findViewForElement(child, element); + if (result != null) { + return result; + } + } + + return null; + } + + /** Returns the {@link Element} for the given {@link CanvasViewInfo} */ + private static Element getElement(CanvasViewInfo info) { + Node node = info.getUiViewNode().getXmlNode(); + if (node instanceof Element) { + return (Element) node; + } + + return null; + } + + /** + * A grid of cells which can contain views, used to infer spatial relationships when + * computing constraints. Note that a view can appear in than one cell; they will + * appear in all cells that their bounds overlap with! + */ + private class Grid { + private final int[] mLeft; + private final int[] mTop; + // A list from row to column to cell, where a cell is a list of views + private final List<List<List<View>>> mRowList; + private int mRowCount; + private int mColCount; + + Grid(List<View> views, int[] left, int[] top) { + mLeft = left; + mTop = top; + + // The left/top arrays should include the ending point too + mColCount = left.length - 1; + mRowCount = top.length - 1; + + // Using nested lists rather than arrays to avoid lack of typed arrays + // (can't create List<View>[row][column] arrays) + mRowList = new ArrayList<List<List<View>>>(top.length); + for (int row = 0; row < top.length; row++) { + List<List<View>> columnList = new ArrayList<List<View>>(left.length); + for (int col = 0; col < left.length; col++) { + columnList.add(new ArrayList<View>(4)); + } + mRowList.add(columnList); + } + + for (View view : views) { + // Get rid of the root view; we don't want that in the attachments logic; + // it was there originally such that it would contribute the outermost + // edges. + if (view.mElement == mLayout) { + continue; + } + + for (int i = 0; i < view.mRowSpan; i++) { + for (int j = 0; j < view.mColSpan; j++) { + mRowList.get(view.mRow + i).get(view.mCol + j).add(view); + } + } + } + } + + /** + * Returns the number of rows in the grid + * + * @return the row count + */ + public int getRows() { + return mRowCount; + } + + /** + * Returns the number of columns in the grid + * + * @return the column count + */ + public int getColumns() { + return mColCount; + } + + /** + * Returns the list of views overlapping the given cell + * + * @param row the row of the target cell + * @param col the column of the target cell + * @return a list of views overlapping the given column + */ + public List<View> get(int row, int col) { + return mRowList.get(row).get(col); + } + + /** + * Returns true if the given column contains a top left corner of a view + * + * @param column the column to check + * @return true if one or more views have their top left corner in this column + */ + public boolean colContainsTopLeftCorner(int column) { + for (int row = 0; row < mRowCount; row++) { + View view = getTopLeftCorner(row, column); + if (view != null) { + return true; + } + } + + return false; + } + + /** + * Returns true if the given row contains a top left corner of a view + * + * @param row the row to check + * @return true if one or more views have their top left corner in this row + */ + public boolean rowContainsTopLeftCorner(int row) { + for (int col = 0; col < mColCount; col++) { + View view = getTopLeftCorner(row, col); + if (view != null) { + return true; + } + } + + return false; + } + + /** + * Returns a list of views (optionally sorted by increasing row index) that have + * their left edge starting in the given column + * + * @param col the column to look up views for + * @param sort whether to sort the result in increasing row order + * @return a list of views starting in the given column + */ + public List<View> viewsStartingInCol(int col, boolean sort) { + List<View> views = new ArrayList<View>(); + for (int row = 0; row < mRowCount; row++) { + View view = getTopLeftCorner(row, col); + if (view != null) { + views.add(view); + } + } + + if (sort) { + View.sortByRow(views); + } + + return views; + } + + /** + * Returns a list of views (optionally sorted by increasing column index) that have + * their top edge starting in the given row + * + * @param row the row to look up views for + * @param sort whether to sort the result in increasing column order + * @return a list of views starting in the given row + */ + public List<View> viewsStartingInRow(int row, boolean sort) { + List<View> views = new ArrayList<View>(); + for (int col = 0; col < mColCount; col++) { + View view = getTopLeftCorner(row, col); + if (view != null) { + views.add(view); + } + } + + if (sort) { + View.sortByColumn(views); + } + + return views; + } + + /** + * Returns the pixel width of the given column + * + * @param col the column to look up the width of + * @return the width of the column + */ + public int getColumnWidth(int col) { + return mLeft[col + 1] - mLeft[col]; + } + + /** + * Returns the pixel height of the given row + * + * @param row the row to look up the height of + * @return the height of the row + */ + public int getRowHeight(int row) { + return mTop[row + 1] - mTop[row]; + } + + /** + * Returns the first view found that has its top left corner in the cell given by + * the row and column indexes, or null if not found. + * + * @param row the row of the target cell + * @param col the column of the target cell + * @return a view with its top left corner in the given cell, or null if not found + */ + View getTopLeftCorner(int row, int col) { + List<View> views = get(row, col); + if (views.size() > 0) { + for (View view : views) { + if (view.mRow == row && view.mCol == col) { + return view; + } + } + } + + return null; + } + + public View findRightEdgeView(int col) { + for (int row = 0; row < mRowCount; row++) { + List<View> views = get(row, col); + if (views.size() > 0) { + List<View> result = new ArrayList<View>(); + for (View view : views) { + // Ends on the right edge of this column? + if (view.mCol + view.mColSpan == col + 1) { + result.add(view); + } + } + if (result.size() > 1) { + View.sortByColumn(result); + } + if (result.size() > 0) { + return result.get(0); + } + } + } + + return null; + } + + public View findBottomEdgeView(int row) { + for (int col = 0; col < mColCount; col++) { + List<View> views = get(row, col); + if (views.size() > 0) { + List<View> result = new ArrayList<View>(); + for (View view : views) { + // Ends on the bottom edge of this column? + if (view.mRow + view.mRowSpan == row + 1) { + result.add(view); + } + } + if (result.size() > 1) { + View.sortByRow(result); + } + if (result.size() > 0) { + return result.get(0); + } + + } + } + + return null; + } + + /** + * Produces a display of view contents along with the pixel positions of each row/column, + * like the following (used for diagnostics only) + * <pre> + * |0 |49 |143 |192 |240 + * 36| | |button2 | + * 72| |radioButton1 |button2 | + * 74|button1 |radioButton1 |button2 | + * 108|button1 | |button2 | + * 110| | |button2 | + * 149| | | | + * 320 + * </pre> + */ + @Override + public String toString() { + // Dump out the view table + int cellWidth = 20; + + StringWriter stringWriter = new StringWriter(); + PrintWriter out = new PrintWriter(stringWriter); + out.printf("%" + cellWidth + "s", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + for (int col = 0; col < mColCount + 1; col++) { + out.printf("|%-" + (cellWidth - 1) + "d", mLeft[col]); //$NON-NLS-1$ //$NON-NLS-2$ + } + out.printf("\n"); //$NON-NLS-1$ + for (int row = 0; row < mRowCount + 1; row++) { + out.printf("%" + cellWidth + "d", mTop[row]); //$NON-NLS-1$ //$NON-NLS-2$ + if (row == mRowCount) { + break; + } + for (int col = 0; col < mColCount; col++) { + List<View> views = get(row, col); + StringBuilder sb = new StringBuilder(); + for (View view : views) { + String id = view != null ? view.getId() : ""; //$NON-NLS-1$ + if (id.startsWith(NEW_ID_PREFIX)) { + id = id.substring(NEW_ID_PREFIX.length()); + } + if (id.length() > cellWidth - 2) { + id = id.substring(0, cellWidth - 2); + } + if (sb.length() > 0) { + sb.append(","); //$NON-NLS-1$ + } + sb.append(id); + } + String cellString = sb.toString(); + if (cellString.contains(",") && cellString.length() > cellWidth - 2) { //$NON-NLS-1$ + cellString = cellString.substring(0, cellWidth - 6) + "...,"; //$NON-NLS-1$ + } + out.printf("|%-" + (cellWidth - 2) + "s ", cellString); //$NON-NLS-1$ //$NON-NLS-2$ + } + out.printf("\n"); //$NON-NLS-1$ + } + + out.flush(); + return stringWriter.toString(); + } + } + + /** Holds layout information about an individual view. */ + private static class View { + private final Element mElement; + private int mRow = -1; + private int mCol = -1; + private int mRowSpan = -1; + private int mColSpan = -1; + private CanvasViewInfo mInfo; + private String mId; + private List<Pair<String, String>> mHorizConstraints = + new ArrayList<Pair<String, String>>(4); + private List<Pair<String, String>> mVerticalConstraints = + new ArrayList<Pair<String, String>>(4); + private int mGravity; + + public View(CanvasViewInfo view, Element element) { + mInfo = view; + mElement = element; + mGravity = RelativeLayoutConversionHelper.getGravity(element); + } + + public int getHeight() { + return mInfo.getAbsRect().height; + } + + public int getGravity() { + return mGravity; + } + + public String getId() { + return mId; + } + + public Element getElement() { + return mElement; + } + + public List<Pair<String, String>> getHorizConstraints() { + return mHorizConstraints; + } + + public List<Pair<String, String>> getVerticalConstraints() { + return mVerticalConstraints; + } + + public boolean isConstrainedHorizontally() { + return mHorizConstraints.size() > 0; + } + + public boolean isConstrainedVertically() { + return mVerticalConstraints.size() > 0; + } + + public void addHorizConstraint(String property, String value) { + assert property != null && value != null; + // TODO - look for duplicates? + mHorizConstraints.add(Pair.of(property, value)); + } + + public void addVerticalConstraint(String property, String value) { + assert property != null && value != null; + mVerticalConstraints.add(Pair.of(property, value)); + } + + public int getLeftEdge() { + return mInfo.getAbsRect().x; + } + + public int getTopEdge() { + return mInfo.getAbsRect().y; + } + + public int getRightEdge() { + Rectangle bounds = mInfo.getAbsRect(); + // +1: make the bounds overlap, so the right edge is the same as the + // left edge of the neighbor etc. Otherwise we end up with lots of 1-pixel wide + // columns between adjacent items. + return bounds.x + bounds.width + 1; + } + + public int getBottomEdge() { + Rectangle bounds = mInfo.getAbsRect(); + return bounds.y + bounds.height + 1; + } + + @Override + public String toString() { + return "View [mId=" + mId + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + public static void sortByRow(List<View> views) { + Collections.sort(views, new ViewComparator(true/*rowSort*/)); + } + + public static void sortByColumn(List<View> views) { + Collections.sort(views, new ViewComparator(false/*rowSort*/)); + } + + /** Comparator to help sort views by row or column index */ + private static class ViewComparator implements Comparator<View> { + boolean mRowSort; + + public ViewComparator(boolean rowSort) { + mRowSort = rowSort; + } + + public int compare(View view1, View view2) { + if (mRowSort) { + return view1.mRow - view2.mRow; + } else { + return view1.mCol - view2.mCol; + } + } + } + } + + /** + * An edge list takes a hierarchy of elements and records the bounds of each element + * into various lists such that it can answer queries about shared edges, about which + * particular pixels occur as a boundary edge, etc. + */ + private class EdgeList { + private final Map<Element, View> mElementToViewMap = new HashMap<Element, View>(100); + private final Map<String, View> mIdToViewMap = new HashMap<String, View>(100); + private final Map<Integer, List<View>> mLeft = new HashMap<Integer, List<View>>(); + private final Map<Integer, List<View>> mTop = new HashMap<Integer, List<View>>(); + private final Map<Integer, List<View>> mRight = new HashMap<Integer, List<View>>(); + private final Map<Integer, List<View>> mBottom = new HashMap<Integer, List<View>>(); + private final Map<Element, Element> mSharedLeftEdge = new HashMap<Element, Element>(); + private final Map<Element, Element> mSharedTopEdge = new HashMap<Element, Element>(); + private final Map<Element, Element> mSharedRightEdge = new HashMap<Element, Element>(); + private final Map<Element, Element> mSharedBottomEdge = new HashMap<Element, Element>(); + private final List<Element> mDelete = new ArrayList<Element>(); + + EdgeList(CanvasViewInfo view) { + analyze(view, true); + mDelete.remove(getElement(view)); + } + + public void setIdAttributeValue(View view, String id) { + assert id.startsWith(NEW_ID_PREFIX) || id.startsWith(ID_PREFIX); + view.mId = id; + mIdToViewMap.put(getIdBasename(id), view); + } + + public View getView(Element element) { + return mElementToViewMap.get(element); + } + + public View getView(String id) { + return mIdToViewMap.get(id); + } + + public List<View> getTopEdgeViews(Integer topOffset) { + return mTop.get(topOffset); + } + + public List<View> getLeftEdgeViews(Integer leftOffset) { + return mLeft.get(leftOffset); + } + + void record(Map<Integer, List<View>> map, Integer edge, View info) { + List<View> list = map.get(edge); + if (list == null) { + list = new ArrayList<View>(); + map.put(edge, list); + } + list.add(info); + } + + private List<Integer> getOffsets(Set<Integer> first, Set<Integer> second) { + Set<Integer> joined = new HashSet<Integer>(first.size() + second.size()); + joined.addAll(first); + joined.addAll(second); + List<Integer> unique = new ArrayList<Integer>(joined); + Collections.sort(unique); + + return unique; + } + + public List<Element> getDeletedElements() { + return mDelete; + } + + public List<Integer> getColumnOffsets() { + return getOffsets(mLeft.keySet(), mRight.keySet()); + } + public List<Integer> getRowOffsets() { + return getOffsets(mTop.keySet(), mBottom.keySet()); + } + + private View analyze(CanvasViewInfo view, boolean isRoot) { + View added = null; + if (!mFlatten || !isRemovableLayout(view)) { + added = add(view); + if (!isRoot) { + return added; + } + } else { + mDelete.add(getElement(view)); + } + + Element parentElement = getElement(view); + Rectangle parentBounds = view.getAbsRect(); + + // Build up a table model of the view + for (CanvasViewInfo child : view.getChildren()) { + Rectangle childBounds = child.getAbsRect(); + Element childElement = getElement(child); + + // See if this view shares the edge with the removed + // parent layout, and if so, record that such that we can + // later handle attachments to the removed parent edges + if (parentBounds.x == childBounds.x) { + mSharedLeftEdge.put(childElement, parentElement); + } + if (parentBounds.y == childBounds.y) { + mSharedTopEdge.put(childElement, parentElement); + } + if (parentBounds.x + parentBounds.width == childBounds.x + childBounds.width) { + mSharedRightEdge.put(childElement, parentElement); + } + if (parentBounds.y + parentBounds.height == childBounds.y + childBounds.height) { + mSharedBottomEdge.put(childElement, parentElement); + } + + if (mFlatten && isRemovableLayout(child)) { + // When flattening, we want to disregard all layouts and instead + // add their children! + for (CanvasViewInfo childView : child.getChildren()) { + analyze(childView, false); + + Element childViewElement = getElement(childView); + Rectangle childViewBounds = childView.getAbsRect(); + + // See if this view shares the edge with the removed + // parent layout, and if so, record that such that we can + // later handle attachments to the removed parent edges + if (parentBounds.x == childViewBounds.x) { + mSharedLeftEdge.put(childViewElement, parentElement); + } + if (parentBounds.y == childViewBounds.y) { + mSharedTopEdge.put(childViewElement, parentElement); + } + if (parentBounds.x + parentBounds.width == childViewBounds.x + + childViewBounds.width) { + mSharedRightEdge.put(childViewElement, parentElement); + } + if (parentBounds.y + parentBounds.height == childViewBounds.y + + childViewBounds.height) { + mSharedBottomEdge.put(childViewElement, parentElement); + } + } + mDelete.add(childElement); + } else { + analyze(child, false); + } + } + + return added; + } + + public View getSharedLeftEdge(Element element) { + return getSharedEdge(element, mSharedLeftEdge); + } + + public View getSharedRightEdge(Element element) { + return getSharedEdge(element, mSharedRightEdge); + } + + public View getSharedTopEdge(Element element) { + return getSharedEdge(element, mSharedTopEdge); + } + + public View getSharedBottomEdge(Element element) { + return getSharedEdge(element, mSharedBottomEdge); + } + + private View getSharedEdge(Element element, Map<Element, Element> sharedEdgeMap) { + Element original = element; + + while (element != null) { + View view = getView(element); + if (view != null) { + assert isAncestor(element, original); + return view; + } + element = sharedEdgeMap.get(element); + } + + return null; + } + + private View add(CanvasViewInfo info) { + Rectangle bounds = info.getAbsRect(); + Element element = getElement(info); + View view = new View(info, element); + mElementToViewMap.put(element, view); + record(mLeft, Integer.valueOf(bounds.x), view); + record(mTop, Integer.valueOf(bounds.y), view); + record(mRight, Integer.valueOf(view.getRightEdge()), view); + record(mBottom, Integer.valueOf(view.getBottomEdge()), view); + return view; + } + + /** + * Returns true if the given {@link CanvasViewInfo} represents an element we + * should remove in a flattening conversion. We don't want to remove non-layout + * views, or layout views that for example contain drawables on their own. + */ + private boolean isRemovableLayout(CanvasViewInfo child) { + // The element being converted is NOT removable! + Element element = getElement(child); + if (element == mLayout) { + return false; + } + + ElementDescriptor descriptor = child.getUiViewNode().getDescriptor(); + String name = descriptor.getXmlLocalName(); + if (name.equals(LINEAR_LAYOUT) || name.equals(RELATIVE_LAYOUT)) { + // Don't delete layouts that provide a background image or gradient + if (element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)) { + AdtPlugin.log(IStatus.WARNING, + "Did not flatten layout %1$s because it defines a '%2$s' attribute", + VisualRefactoring.getId(element), ATTR_BACKGROUND); + return false; + } + + return true; + } + + return false; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java new file mode 100644 index 0000000..87fdc14 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java @@ -0,0 +1,1281 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; +import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_COLON; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.ChangeDescriptor; +import org.eclipse.ltk.core.refactoring.CompositeChange; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.text.edits.DeleteEdit; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.ide.IDE; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList; +import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Parent class for the various visual refactoring operations; contains shared + * implementations needed by most of them + */ +@SuppressWarnings("restriction") // XML model +public abstract class VisualRefactoring extends Refactoring { + private static final String KEY_FILE = "file"; //$NON-NLS-1$ + private static final String KEY_PROJECT = "proj"; //$NON-NLS-1$ + private static final String KEY_SEL_START = "sel-start"; //$NON-NLS-1$ + private static final String KEY_SEL_END = "sel-end"; //$NON-NLS-1$ + + protected final IFile mFile; + protected final LayoutEditor mEditor; + protected final IProject mProject; + protected int mSelectionStart = -1; + protected int mSelectionEnd = -1; + protected final List<Element> mElements; + protected final ITreeSelection mTreeSelection; + protected final ITextSelection mSelection; + /** Same as {@link #mSelectionStart} but not adjusted to element edges */ + protected int mOriginalSelectionStart = -1; + /** Same as {@link #mSelectionEnd} but not adjusted to element edges */ + protected int mOriginalSelectionEnd = -1; + + protected final Map<Element, String> mGeneratedIdMap = new HashMap<Element, String>(); + protected final Set<String> mGeneratedIds = new HashSet<String>(); + + protected List<Change> mChanges; + private String mAndroidNamespacePrefix; + + /** + * This constructor is solely used by {@link VisualRefactoringDescriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + VisualRefactoring(Map<String, String> arguments) { + IPath path = Path.fromPortableString(arguments.get(KEY_PROJECT)); + mProject = (IProject) ResourcesPlugin.getWorkspace().getRoot().findMember(path); + path = Path.fromPortableString(arguments.get(KEY_FILE)); + mFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(path); + mSelectionStart = Integer.parseInt(arguments.get(KEY_SEL_START)); + mSelectionEnd = Integer.parseInt(arguments.get(KEY_SEL_END)); + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; + mEditor = null; + mElements = null; + mSelection = null; + mTreeSelection = null; + } + + @VisibleForTesting + VisualRefactoring(List<Element> elements, LayoutEditor editor) { + mElements = elements; + mEditor = editor; + + mFile = editor != null ? editor.getInputFile() : null; + mProject = editor != null ? editor.getProject() : null; + mSelectionStart = 0; + mSelectionEnd = 0; + mOriginalSelectionStart = 0; + mOriginalSelectionEnd = 0; + mSelection = null; + mTreeSelection = null; + + int end = Integer.MIN_VALUE; + int start = Integer.MAX_VALUE; + for (Element element : elements) { + if (element instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) element; + start = Math.min(start, region.getStartOffset()); + end = Math.max(end, region.getEndOffset()); + } + } + if (start >= 0) { + mSelectionStart = start; + mSelectionEnd = end; + mOriginalSelectionStart = start; + mOriginalSelectionEnd = end; + } + } + + public VisualRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + mFile = file; + mEditor = editor; + mProject = file.getProject(); + mSelection = selection; + mTreeSelection = treeSelection; + + // Initialize mSelectionStart and mSelectionEnd based on the selection context, which + // is either a treeSelection (when invoked from the layout editor or the outline), or + // a selection (when invoked from an XML editor) + if (treeSelection != null) { + int end = Integer.MIN_VALUE; + int start = Integer.MAX_VALUE; + for (TreePath path : treeSelection.getPaths()) { + Object lastSegment = path.getLastSegment(); + if (lastSegment instanceof CanvasViewInfo) { + CanvasViewInfo viewInfo = (CanvasViewInfo) lastSegment; + UiViewElementNode uiNode = viewInfo.getUiViewNode(); + if (uiNode == null) { + continue; + } + Node xmlNode = uiNode.getXmlNode(); + if (xmlNode instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) xmlNode; + + start = Math.min(start, region.getStartOffset()); + end = Math.max(end, region.getEndOffset()); + } + } + } + if (start >= 0) { + mSelectionStart = start; + mSelectionEnd = end; + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; + } + if (selection != null) { + mOriginalSelectionStart = selection.getOffset(); + mOriginalSelectionEnd = mOriginalSelectionStart + selection.getLength(); + } + } else if (selection != null) { + // TODO: update selection to boundaries! + mSelectionStart = selection.getOffset(); + mSelectionEnd = mSelectionStart + selection.getLength(); + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; + } + + mElements = initElements(); + } + + protected abstract List<Change> computeChanges(IProgressMonitor monitor); + + @Override + public RefactoringStatus checkFinalConditions(IProgressMonitor monitor) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + mChanges = new ArrayList<Change>(); + try { + monitor.beginTask("Checking post-conditions...", 5); + + // Reset state for each computeChanges call, in case the user goes back + // and forth in the refactoring wizard + mGeneratedIdMap.clear(); + mGeneratedIds.clear(); + List<Change> changes = computeChanges(monitor); + mChanges.addAll(changes); + + monitor.worked(1); + } finally { + monitor.done(); + } + + return status; + } + + @Override + public Change createChange(IProgressMonitor monitor) throws CoreException, + OperationCanceledException { + try { + monitor.beginTask("Applying changes...", 1); + + CompositeChange change = new CompositeChange( + getName(), + mChanges.toArray(new Change[mChanges.size()])) { + @Override + public ChangeDescriptor getDescriptor() { + VisualRefactoringDescriptor desc = createDescriptor(); + return new RefactoringChangeDescriptor(desc); + } + }; + + monitor.worked(1); + return change; + + } finally { + monitor.done(); + } + } + + protected abstract VisualRefactoringDescriptor createDescriptor(); + + protected Map<String, String> createArgumentMap() { + HashMap<String, String> args = new HashMap<String, String>(); + args.put(KEY_PROJECT, mProject.getFullPath().toPortableString()); + args.put(KEY_FILE, mFile.getFullPath().toPortableString()); + args.put(KEY_SEL_START, Integer.toString(mSelectionStart)); + args.put(KEY_SEL_END, Integer.toString(mSelectionEnd)); + + return args; + } + + // ---- Shared functionality ---- + + + protected void openFile(IFile file) { + GraphicalEditorPart graphicalEditor = mEditor.getGraphicalEditor(); + IFile leavingFile = graphicalEditor.getEditedFile(); + + try { + // Duplicate the current state into the newly created file + QualifiedName qname = ConfigurationComposite.NAME_CONFIG_STATE; + String state = AdtPlugin.getFileProperty(leavingFile, qname); + + // TODO: Look for a ".NoTitleBar.Fullscreen" theme version of the current + // theme to show. + + file.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE, state); + } catch (CoreException e) { + // pass + } + + /* TBD: "Show Included In" if supported. + * Not sure if this is a good idea. + if (graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) { + try { + Reference include = Reference.create(graphicalEditor.getEditedFile()); + file.setSessionProperty(GraphicalEditorPart.NAME_INCLUDE, include); + } catch (CoreException e) { + // pass - worst that can happen is that we don't start with inclusion + } + } + */ + + try { + IEditorPart part = IDE.openEditor(mEditor.getEditorSite().getPage(), file); + if (part instanceof AndroidXmlEditor && AdtPrefs.getPrefs().getFormatXml()) { + AndroidXmlEditor newEditor = (AndroidXmlEditor) part; + newEditor.reformatDocument(); + } + } catch (PartInitException e) { + AdtPlugin.log(e, "Can't open new included layout"); + } + } + + + /** Produce a list of edits to replace references to the given id with the given new id */ + protected static List<TextEdit> replaceIds(String androidNamePrefix, + IStructuredDocument doc, int skipStart, int skipEnd, + String rootId, String referenceId) { + if (rootId == null) { + return Collections.emptyList(); + } + + // We need to search for either @+id/ or @id/ + String match1 = rootId; + String match2; + if (match1.startsWith(ID_PREFIX)) { + match2 = '"' + NEW_ID_PREFIX + match1.substring(ID_PREFIX.length()) + '"'; + match1 = '"' + match1 + '"'; + } else if (match1.startsWith(NEW_ID_PREFIX)) { + match2 = '"' + ID_PREFIX + match1.substring(NEW_ID_PREFIX.length()) + '"'; + match1 = '"' + match1 + '"'; + } else { + return Collections.emptyList(); + } + + String namePrefix = androidNamePrefix + ':' + ATTR_LAYOUT_PREFIX; + List<TextEdit> edits = new ArrayList<TextEdit>(); + + IStructuredDocumentRegion region = doc.getFirstStructuredDocumentRegion(); + for (; region != null; region = region.getNext()) { + ITextRegionList list = region.getRegions(); + int regionStart = region.getStart(); + + // Look at all attribute values and look for an id reference match + String attributeName = ""; //$NON-NLS-1$ + for (int j = 0; j < region.getNumberOfRegions(); j++) { + ITextRegion subRegion = list.get(j); + String type = subRegion.getType(); + if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) { + attributeName = region.getText(subRegion); + } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { + // Only replace references in layout attributes + if (!attributeName.startsWith(namePrefix)) { + continue; + } + // Skip occurrences in the given skip range + int subRegionStart = regionStart + subRegion.getStart(); + if (subRegionStart >= skipStart && subRegionStart <= skipEnd) { + continue; + } + + String attributeValue = region.getText(subRegion); + if (attributeValue.equals(match1) || attributeValue.equals(match2)) { + int start = subRegionStart + 1; // skip quote + int end = start + rootId.length(); + + edits.add(new ReplaceEdit(start, end - start, referenceId)); + } + } + } + } + + return edits; + } + + /** Get the id of the root selected element, if any */ + protected String getRootId() { + Element primary = getPrimaryElement(); + if (primary != null) { + String oldId = primary.getAttributeNS(ANDROID_URI, ATTR_ID); + // id null check for https://bugs.eclipse.org/bugs/show_bug.cgi?id=272378 + if (oldId != null && oldId.length() > 0) { + return oldId; + } + } + + return null; + } + + protected String getAndroidNamespacePrefix() { + if (mAndroidNamespacePrefix == null) { + List<Attr> attributeNodes = findNamespaceAttributes(); + for (Node attributeNode : attributeNodes) { + String prefix = attributeNode.getPrefix(); + if (XMLNS.equals(prefix)) { + String name = attributeNode.getNodeName(); + String value = attributeNode.getNodeValue(); + if (value.equals(ANDROID_URI)) { + mAndroidNamespacePrefix = name; + if (mAndroidNamespacePrefix.startsWith(XMLNS_COLON)) { + mAndroidNamespacePrefix = + mAndroidNamespacePrefix.substring(XMLNS_COLON.length()); + } + } + } + } + + if (mAndroidNamespacePrefix == null) { + mAndroidNamespacePrefix = ANDROID_NS_NAME; + } + } + + return mAndroidNamespacePrefix; + } + + protected static String getAndroidNamespacePrefix(Document document) { + String nsPrefix = null; + List<Attr> attributeNodes = findNamespaceAttributes(document); + for (Node attributeNode : attributeNodes) { + String prefix = attributeNode.getPrefix(); + if (XMLNS.equals(prefix)) { + String name = attributeNode.getNodeName(); + String value = attributeNode.getNodeValue(); + if (value.equals(ANDROID_URI)) { + nsPrefix = name; + if (nsPrefix.startsWith(XMLNS_COLON)) { + nsPrefix = + nsPrefix.substring(XMLNS_COLON.length()); + } + } + } + } + + if (nsPrefix == null) { + nsPrefix = ANDROID_NS_NAME; + } + + return nsPrefix; + } + + protected List<Attr> findNamespaceAttributes() { + Document document = getDomDocument(); + return findNamespaceAttributes(document); + } + + protected static List<Attr> findNamespaceAttributes(Document document) { + if (document != null) { + Element root = document.getDocumentElement(); + return findNamespaceAttributes(root); + } + + return Collections.emptyList(); + } + + protected static List<Attr> findNamespaceAttributes(Node root) { + List<Attr> result = new ArrayList<Attr>(); + NamedNodeMap attributes = root.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node attributeNode = attributes.item(i); + + String prefix = attributeNode.getPrefix(); + if (XMLNS.equals(prefix)) { + result.add((Attr) attributeNode); + } + } + + return result; + } + + protected List<Attr> findLayoutAttributes(Node root) { + List<Attr> result = new ArrayList<Attr>(); + NamedNodeMap attributes = root.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node attributeNode = attributes.item(i); + + String name = attributeNode.getLocalName(); + if (name.startsWith(ATTR_LAYOUT_PREFIX) + && ANDROID_URI.equals(attributeNode.getNamespaceURI())) { + result.add((Attr) attributeNode); + } + } + + return result; + } + + protected String insertNamespace(String xmlText, String namespaceDeclarations) { + // Insert namespace declarations into the extracted XML fragment + int firstSpace = xmlText.indexOf(' '); + int elementEnd = xmlText.indexOf('>'); + int insertAt; + if (firstSpace != -1 && firstSpace < elementEnd) { + insertAt = firstSpace; + } else { + insertAt = elementEnd; + } + xmlText = xmlText.substring(0, insertAt) + namespaceDeclarations + + xmlText.substring(insertAt); + + return xmlText; + } + + /** Remove sections of the document that correspond to top level layout attributes; + * these are placed on the include element instead */ + protected String stripTopLayoutAttributes(Element primary, int start, String xml) { + if (primary != null) { + // List of attributes to remove + List<IndexedRegion> skip = new ArrayList<IndexedRegion>(); + NamedNodeMap attributes = primary.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node attr = attributes.item(i); + String name = attr.getLocalName(); + if (name.startsWith(ATTR_LAYOUT_PREFIX) + && ANDROID_URI.equals(attr.getNamespaceURI())) { + if (name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) { + // These are special and are left in + continue; + } + + if (attr instanceof IndexedRegion) { + skip.add((IndexedRegion) attr); + } + } + } + if (skip.size() > 0) { + Collections.sort(skip, new Comparator<IndexedRegion>() { + // Sort in start order + public int compare(IndexedRegion r1, IndexedRegion r2) { + return r1.getStartOffset() - r2.getStartOffset(); + } + }); + + // Successively cut out the various layout attributes + // TODO remove adjacent whitespace too (but not newlines, unless they + // are newly adjacent) + StringBuilder sb = new StringBuilder(xml.length()); + int nextStart = 0; + + // Copy out all the sections except the skip sections + for (IndexedRegion r : skip) { + int regionStart = r.getStartOffset(); + // Adjust to string offsets since we've copied the string out of + // the document + regionStart -= start; + + sb.append(xml.substring(nextStart, regionStart)); + + nextStart = regionStart + r.getLength(); + } + if (nextStart < xml.length()) { + sb.append(xml.substring(nextStart)); + } + + return sb.toString(); + } + } + + return xml; + } + + protected static String getIndent(String line, int max) { + int i = 0; + int n = Math.min(max, line.length()); + for (; i < n; i++) { + char c = line.charAt(i); + if (!Character.isWhitespace(c)) { + return line.substring(0, i); + } + } + + if (n < line.length()) { + return line.substring(0, n); + } else { + return line; + } + } + + protected static String dedent(String xml) { + String[] lines = xml.split("\n"); //$NON-NLS-1$ + if (lines.length < 2) { + // The first line never has any indentation since we copy it out from the + // element start index + return xml; + } + + String indentPrefix = getIndent(lines[1], lines[1].length()); + for (int i = 2, n = lines.length; i < n; i++) { + String line = lines[i]; + + // Ignore blank lines + if (line.trim().length() == 0) { + continue; + } + + indentPrefix = getIndent(line, indentPrefix.length()); + + if (indentPrefix.length() == 0) { + return xml; + } + } + + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + if (line.startsWith(indentPrefix)) { + sb.append(line.substring(indentPrefix.length())); + } else { + sb.append(line); + } + sb.append('\n'); + } + return sb.toString(); + } + + protected String getText(int start, int end) { + try { + IStructuredDocument document = mEditor.getStructuredDocument(); + return document.get(start, end - start); + } catch (BadLocationException e) { + // the region offset was invalid. ignore. + return null; + } + } + + protected List<Element> getElements() { + return mElements; + } + + protected List<Element> initElements() { + List<Element> nodes = new ArrayList<Element>(); + + assert mTreeSelection == null || mSelection == null : + "treeSel= " + mTreeSelection + ", sel=" + mSelection; + + // Initialize mSelectionStart and mSelectionEnd based on the selection context, which + // is either a treeSelection (when invoked from the layout editor or the outline), or + // a selection (when invoked from an XML editor) + if (mTreeSelection != null) { + int end = Integer.MIN_VALUE; + int start = Integer.MAX_VALUE; + for (TreePath path : mTreeSelection.getPaths()) { + Object lastSegment = path.getLastSegment(); + if (lastSegment instanceof CanvasViewInfo) { + CanvasViewInfo viewInfo = (CanvasViewInfo) lastSegment; + UiViewElementNode uiNode = viewInfo.getUiViewNode(); + if (uiNode == null) { + continue; + } + Node xmlNode = uiNode.getXmlNode(); + if (xmlNode instanceof Element) { + Element element = (Element) xmlNode; + nodes.add(element); + IndexedRegion region = getRegion(element); + start = Math.min(start, region.getStartOffset()); + end = Math.max(end, region.getEndOffset()); + } + } + } + if (start >= 0) { + mSelectionStart = start; + mSelectionEnd = end; + } + } else if (mSelection != null) { + mSelectionStart = mSelection.getOffset(); + mSelectionEnd = mSelectionStart + mSelection.getLength(); + mOriginalSelectionStart = mSelectionStart; + mOriginalSelectionEnd = mSelectionEnd; + + // Figure out the range of selected nodes from the document offsets + IStructuredDocument doc = mEditor.getStructuredDocument(); + Pair<Element, Element> range = DomUtilities.getElementRange(doc, + mSelectionStart, mSelectionEnd); + if (range != null) { + Element first = range.getFirst(); + Element last = range.getSecond(); + + // Adjust offsets to get rid of surrounding text nodes (if you happened + // to select a text range and included whitespace on either end etc) + mSelectionStart = getRegion(first).getStartOffset(); + mSelectionEnd = getRegion(last).getEndOffset(); + + if (first == last) { + nodes.add(first); + } else if (first.getParentNode() == last.getParentNode()) { + // Add the range + Node node = first; + while (node != null) { + if (node instanceof Element) { + nodes.add((Element) node); + } + if (node == last) { + break; + } + node = node.getNextSibling(); + } + } else { + // Different parents: this means we have an uneven selection, selecting + // elements from different levels. We can't extract ranges like that. + } + } + } else { + assert false; + } + + // Make sure that the list of elements is unique + //Set<Element> seen = new HashSet<Element>(); + //for (Element element : nodes) { + // assert !seen.contains(element) : element; + // seen.add(element); + //} + + return nodes; + } + + protected Element getPrimaryElement() { + List<Element> elements = getElements(); + if (elements != null && elements.size() == 1) { + return elements.get(0); + } + + return null; + } + + protected Document getDomDocument() { + if (mEditor.getUiRootNode() != null) { + return mEditor.getUiRootNode().getXmlDocument(); + } else { + return getElements().get(0).getOwnerDocument(); + } + } + + protected List<CanvasViewInfo> getSelectedViewInfos() { + List<CanvasViewInfo> infos = new ArrayList<CanvasViewInfo>(); + if (mTreeSelection != null) { + for (TreePath path : mTreeSelection.getPaths()) { + Object lastSegment = path.getLastSegment(); + if (lastSegment instanceof CanvasViewInfo) { + infos.add((CanvasViewInfo) lastSegment); + } + } + } + return infos; + } + + protected boolean validateNotEmpty(List<CanvasViewInfo> infos, RefactoringStatus status) { + if (infos.size() == 0) { + status.addFatalError("No selection to extract"); + return false; + } + + return true; + } + + protected boolean validateNotRoot(List<CanvasViewInfo> infos, RefactoringStatus status) { + for (CanvasViewInfo info : infos) { + if (info.isRoot()) { + status.addFatalError("Cannot refactor the root"); + return false; + } + } + + return true; + } + + protected boolean validateContiguous(List<CanvasViewInfo> infos, RefactoringStatus status) { + if (infos.size() > 1) { + // All elements must be siblings (e.g. same parent) + List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>(infos + .size()); + for (CanvasViewInfo info : infos) { + UiViewElementNode node = info.getUiViewNode(); + if (node != null) { + nodes.add(node); + } + } + if (nodes.size() == 0) { + status.addFatalError("No selected views"); + return false; + } + + UiElementNode parent = nodes.get(0).getUiParent(); + for (UiViewElementNode node : nodes) { + if (parent != node.getUiParent()) { + status.addFatalError("The selected elements must be adjacent"); + return false; + } + } + // Ensure that the siblings are contiguous; no gaps. + // If we've selected all the children of the parent then we don't need + // to look. + List<UiElementNode> siblings = parent.getUiChildren(); + if (siblings.size() != nodes.size()) { + Set<UiViewElementNode> nodeSet = new HashSet<UiViewElementNode>(nodes); + boolean inRange = false; + int remaining = nodes.size(); + for (UiElementNode node : siblings) { + boolean in = nodeSet.contains(node); + if (in) { + remaining--; + if (remaining == 0) { + break; + } + inRange = true; + } else if (inRange) { + status.addFatalError("The selected elements must be adjacent"); + return false; + } + } + } + } + + return true; + } + + /** + * Updates the given element with a new name if the current id reflects the old + * element type. If the name was changed, it will return the new name. + */ + protected String ensureIdMatchesType(Element element, String newType, MultiTextEdit rootEdit) { + String oldType = element.getTagName(); + if (oldType.indexOf('.') == -1) { + oldType = ANDROID_WIDGET_PREFIX + oldType; + } + String oldTypeBase = oldType.substring(oldType.lastIndexOf('.') + 1); + String id = getId(element); + if (id == null || id.toLowerCase().contains(oldTypeBase.toLowerCase())) { + String newTypeBase = newType.substring(newType.lastIndexOf('.') + 1); + return ensureHasId(rootEdit, element, newTypeBase); + } + + return null; + } + + public static IndexedRegion getRegion(Node node) { + if (node instanceof IndexedRegion) { + return (IndexedRegion) node; + } + + return null; + } + + protected String ensureHasId(MultiTextEdit rootEdit, Element element, String prefix) { + String id = mGeneratedIdMap.get(element); + if (id != null) { + return NEW_ID_PREFIX + id; + } + + if (!element.hasAttributeNS(ANDROID_URI, ATTR_ID) + || (prefix != null && !getId(element).startsWith(prefix))) { + id = DomUtilities.getFreeWidgetId(element, mGeneratedIds, prefix); + // Make sure we don't use this one again + mGeneratedIds.add(id); + mGeneratedIdMap.put(element, id); + id = NEW_ID_PREFIX + id; + setAttribute(rootEdit, element, + ANDROID_URI, getAndroidNamespacePrefix(), ATTR_ID, id); + return id; + } + + return getId(element); + } + + protected int getFirstAttributeOffset(Element element) { + IndexedRegion region = getRegion(element); + if (region != null) { + int startOffset = region.getStartOffset(); + int endOffset = region.getEndOffset(); + String text = getText(startOffset, endOffset); + String name = element.getLocalName(); + int nameOffset = text.indexOf(name); + if (nameOffset != -1) { + return startOffset + nameOffset + name.length(); + } + } + + return -1; + } + + public static String getId(Element element) { + return element.getAttributeNS(ANDROID_URI, ATTR_ID); + } + + protected String ensureNewId(String id) { + if (id != null && id.length() > 0) { + if (id.startsWith(ID_PREFIX)) { + id = NEW_ID_PREFIX + id.substring(ID_PREFIX.length()); + } else if (!id.startsWith(NEW_ID_PREFIX)) { + id = NEW_ID_PREFIX + id; + } + } else { + id = null; + } + + return id; + } + + protected String getViewClass(String fqcn) { + // Don't include android.widget. as a package prefix in layout files + if (fqcn.startsWith(ANDROID_WIDGET_PREFIX)) { + fqcn = fqcn.substring(ANDROID_WIDGET_PREFIX.length()); + } + + return fqcn; + } + + protected void setAttribute(MultiTextEdit rootEdit, Element element, + String attributeUri, + String attributePrefix, String attributeName, String attributeValue) { + int offset = getFirstAttributeOffset(element); + if (offset != -1) { + if (element.hasAttributeNS(attributeUri, attributeName)) { + replaceAttributeDeclaration(rootEdit, offset, element, attributePrefix, + attributeUri, attributeName, attributeValue); + } else { + addAttributeDeclaration(rootEdit, offset, attributePrefix, attributeName, + attributeValue); + } + } + } + + private void addAttributeDeclaration(MultiTextEdit rootEdit, int offset, + String attributePrefix, String attributeName, String attributeValue) { + StringBuilder sb = new StringBuilder(); + sb.append(' '); + + if (attributePrefix != null) { + sb.append(attributePrefix).append(':'); + } + sb.append(attributeName).append('=').append('"'); + sb.append(attributeValue).append('"'); + + InsertEdit setAttribute = new InsertEdit(offset, sb.toString()); + rootEdit.addChild(setAttribute); + } + + /** Replaces the value declaration of the given attribute */ + private void replaceAttributeDeclaration(MultiTextEdit rootEdit, int offset, + Element element, String attributePrefix, String attributeUri, + String attributeName, String attributeValue) { + // Find attribute value and replace it + IStructuredModel model = mEditor.getModelForRead(); + try { + IStructuredDocument doc = model.getStructuredDocument(); + + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); + ITextRegionList list = region.getRegions(); + int regionStart = region.getStart(); + + int valueStart = -1; + boolean useNextValue = false; + String targetName = attributePrefix != null + ? attributePrefix + ':' + attributeName : attributeName; + + // Look at all attribute values and look for an id reference match + for (int j = 0; j < region.getNumberOfRegions(); j++) { + ITextRegion subRegion = list.get(j); + String type = subRegion.getType(); + if (DOMRegionContext.XML_TAG_ATTRIBUTE_NAME.equals(type)) { + // What about prefix? + if (targetName.equals(region.getText(subRegion))) { + useNextValue = true; + } + } else if (DOMRegionContext.XML_TAG_ATTRIBUTE_VALUE.equals(type)) { + if (useNextValue) { + valueStart = regionStart + subRegion.getStart(); + break; + } + } + } + + if (valueStart != -1) { + String oldValue = element.getAttributeNS(attributeUri, attributeName); + int start = valueStart + 1; // Skip opening " + ReplaceEdit setAttribute = new ReplaceEdit(start, oldValue.length(), + attributeValue); + try { + rootEdit.addChild(setAttribute); + } catch (MalformedTreeException mte) { + AdtPlugin.log(mte, "Could not replace attribute %1$s with %2$s", + attributeName, attributeValue); + throw mte; + } + } + } finally { + model.releaseFromRead(); + } + } + + /** Strips out the given attribute, if defined */ + protected void removeAttribute(MultiTextEdit rootEdit, Element element, String uri, + String attributeName) { + if (element.hasAttributeNS(uri, attributeName)) { + Attr attribute = element.getAttributeNodeNS(uri, attributeName); + removeAttribute(rootEdit, attribute); + } + } + + /** Strips out the given attribute, if defined */ + protected void removeAttribute(MultiTextEdit rootEdit, Attr attribute) { + IndexedRegion region = getRegion(attribute); + if (region != null) { + int startOffset = region.getStartOffset(); + int endOffset = region.getEndOffset(); + DeleteEdit deletion = new DeleteEdit(startOffset, endOffset - startOffset); + rootEdit.addChild(deletion); + } + } + + + /** + * Removes the given element's opening and closing tags (including all of its + * attributes) but leaves any children alone + * + * @param rootEdit the multi edit to add the removal operation to + * @param element the element to delete the open and closing tags for + * @param skip a list of elements that should not be modified (for example because they + * are targeted for deletion) + * + * TODO: Rename this to "unwrap" ? And allow for handling nested deletions. + */ + protected void removeElementTags(MultiTextEdit rootEdit, Element element, List<Element> skip) { + IndexedRegion elementRegion = getRegion(element); + if (elementRegion == null) { + return; + } + + // Look for the opening tag + IStructuredModel model = mEditor.getModelForRead(); + try { + int startLineInclusive = -1; + int endLineInclusive = -1; + IStructuredDocument doc = model.getStructuredDocument(); + if (doc != null) { + int start = elementRegion.getStartOffset(); + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(start); + ITextRegionList list = region.getRegions(); + int regionStart = region.getStart(); + int startOffset = regionStart; + for (int j = 0; j < region.getNumberOfRegions(); j++) { + ITextRegion subRegion = list.get(j); + String type = subRegion.getType(); + if (DOMRegionContext.XML_TAG_OPEN.equals(type)) { + startOffset = regionStart + subRegion.getStart(); + } else if (DOMRegionContext.XML_TAG_CLOSE.equals(type)) { + int endOffset = regionStart + subRegion.getStart() + subRegion.getLength(); + + DeleteEdit deletion = createDeletion(doc, startOffset, endOffset); + rootEdit.addChild(deletion); + startLineInclusive = doc.getLineOfOffset(endOffset) + 1; + break; + } + } + + + + // Find the close tag + // Look at all attribute values and look for an id reference match + region = doc.getRegionAtCharacterOffset(elementRegion.getEndOffset() + - element.getTagName().length() - 1); + list = region.getRegions(); + regionStart = region.getStartOffset(); + startOffset = -1; + for (int j = 0; j < region.getNumberOfRegions(); j++) { + ITextRegion subRegion = list.get(j); + String type = subRegion.getType(); + if (DOMRegionContext.XML_END_TAG_OPEN.equals(type)) { + startOffset = regionStart + subRegion.getStart(); + } else if (DOMRegionContext.XML_TAG_CLOSE.equals(type)) { + int endOffset = regionStart + subRegion.getStart() + subRegion.getLength(); + if (startOffset != -1) { + DeleteEdit deletion = createDeletion(doc, startOffset, endOffset); + rootEdit.addChild(deletion); + endLineInclusive = doc.getLineOfOffset(startOffset) - 1; + } + break; + } + } + } + + // Dedent the contents + if (startLineInclusive != -1 && endLineInclusive != -1) { + String indent = AndroidXmlEditor.getIndentAtOffset(doc, getRegion(element) + .getStartOffset()); + setIndentation(rootEdit, indent, doc, startLineInclusive, endLineInclusive, + element, skip); + } + } finally { + model.releaseFromRead(); + } + } + + protected void removeIndentation(MultiTextEdit rootEdit, String removeIndent, + IStructuredDocument doc, int startLineInclusive, int endLineInclusive, + Element element, List<Element> skip) { + if (startLineInclusive > endLineInclusive) { + return; + } + int indentLength = removeIndent.length(); + if (indentLength == 0) { + return; + } + + try { + for (int line = startLineInclusive; line <= endLineInclusive; line++) { + IRegion info = doc.getLineInformation(line); + int lineStart = info.getOffset(); + int lineLength = info.getLength(); + int lineEnd = lineStart + lineLength; + if (overlaps(lineStart, lineEnd, element, skip)) { + continue; + } + String lineText = getText(lineStart, + lineStart + Math.min(lineLength, indentLength)); + if (lineText.startsWith(removeIndent)) { + rootEdit.addChild(new DeleteEdit(lineStart, indentLength)); + } + } + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + } + + protected void setIndentation(MultiTextEdit rootEdit, String indent, + IStructuredDocument doc, int startLineInclusive, int endLineInclusive, + Element element, List<Element> skip) { + if (startLineInclusive > endLineInclusive) { + return; + } + int indentLength = indent.length(); + if (indentLength == 0) { + return; + } + + try { + for (int line = startLineInclusive; line <= endLineInclusive; line++) { + IRegion info = doc.getLineInformation(line); + int lineStart = info.getOffset(); + int lineLength = info.getLength(); + int lineEnd = lineStart + lineLength; + if (overlaps(lineStart, lineEnd, element, skip)) { + continue; + } + String lineText = getText(lineStart, lineStart + lineLength); + int indentEnd = getFirstNonSpace(lineText); + rootEdit.addChild(new ReplaceEdit(lineStart, indentEnd, indent)); + } + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + } + + private int getFirstNonSpace(String s) { + for (int i = 0; i < s.length(); i++) { + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + + return s.length(); + } + + /** Returns true if the given line overlaps any of the given elements */ + private static boolean overlaps(int startOffset, int endOffset, + Element element, List<Element> overlaps) { + for (Element e : overlaps) { + if (e == element) { + continue; + } + + IndexedRegion region = getRegion(e); + if (region.getEndOffset() >= startOffset && region.getStartOffset() <= endOffset) { + return true; + } + } + return false; + } + + protected DeleteEdit createDeletion(IStructuredDocument doc, int startOffset, int endOffset) { + // Expand to delete the whole line? + try { + IRegion info = doc.getLineInformationOfOffset(startOffset); + int lineBegin = info.getOffset(); + // Is the text on the line leading up to the deletion region, + // and the text following it, all whitespace? + boolean deleteLine = true; + if (lineBegin < startOffset) { + String prefix = getText(lineBegin, startOffset); + if (prefix.trim().length() > 0) { + deleteLine = false; + } + } + info = doc.getLineInformationOfOffset(endOffset); + int lineEnd = info.getOffset() + info.getLength(); + if (lineEnd > endOffset) { + String suffix = getText(endOffset, lineEnd); + if (suffix.trim().length() > 0) { + deleteLine = false; + } + } + if (deleteLine) { + startOffset = lineBegin; + endOffset = lineEnd + 1; + } + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + + + return new DeleteEdit(startOffset, endOffset - startOffset); + } + + protected ViewElementDescriptor getElementDescriptor(String fqcn) { + AndroidTargetData data = mEditor.getTargetData(); + if (data != null) { + List<ViewElementDescriptor> views = + data.getLayoutDescriptors().getViewDescriptors(); + for (ViewElementDescriptor descriptor : views) { + if (fqcn.equals(descriptor.getFullClassName())) { + return descriptor; + } + } + List<ViewElementDescriptor> layouts = + data.getLayoutDescriptors().getLayoutDescriptors(); + for (ViewElementDescriptor descriptor : layouts) { + if (fqcn.equals(descriptor.getFullClassName())) { + return descriptor; + } + } + } + + return null; + } + + /** Create a wizard for this refactoring */ + abstract VisualRefactoringWizard createWizard(); + + public abstract static class VisualRefactoringDescriptor extends RefactoringDescriptor { + private final Map<String, String> mArguments; + + public VisualRefactoringDescriptor( + String id, String project, String description, String comment, + Map<String, String> arguments) { + super(id, project, description, comment, STRUCTURAL_CHANGE | MULTI_CHANGE); + mArguments = arguments; + } + + public Map<String, String> getArguments() { + return mArguments; + } + + protected abstract Refactoring createRefactoring(Map<String, String> args); + + @Override + public Refactoring createRefactoring(RefactoringStatus status) throws CoreException { + try { + return createRefactoring(mArguments); + } catch (NullPointerException e) { + status.addFatalError("Failed to recreate refactoring from descriptor"); + return null; + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoringAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoringAction.java new file mode 100644 index 0000000..4818132 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoringAction.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.FileEditorInput; + +abstract class VisualRefactoringAction implements IWorkbenchWindowActionDelegate { + protected IWorkbenchWindow mWindow; + protected ITextSelection mTextSelection; + protected ITreeSelection mTreeSelection; + protected LayoutEditor mEditor; + protected IFile mFile; + + /** + * Keep track of the current workbench window. + */ + public void init(IWorkbenchWindow window) { + mWindow = window; + } + + public void dispose() { + } + + /** + * Examine the selection to determine if the action should be enabled or not. + * <p/> + * Keep a link to the relevant selection structure + */ + public void selectionChanged(IAction action, ISelection selection) { + // Look for selections in XML and in the layout UI editor + + // Note, two kinds of selections are returned here: + // ITextSelection on a Java source window + // IStructuredSelection in the outline or navigator + // This simply deals with the refactoring based on a non-empty selection. + // At that point, just enable the action and later decide if it's valid when it actually + // runs since we don't have access to the AST yet. + + mTextSelection = null; + mTreeSelection = null; + mFile = null; + + IEditorPart editor = null; + + if (selection instanceof ITextSelection) { + mTextSelection = (ITextSelection) selection; + editor = getActiveEditor(); + mFile = getSelectedFile(editor); + } else if (selection instanceof ITreeSelection) { + Object firstElement = ((ITreeSelection)selection).getFirstElement(); + if (firstElement instanceof CanvasViewInfo) { + mTreeSelection = (ITreeSelection) selection; + editor = getActiveEditor(); + mFile = getSelectedFile(editor); + } + } + + + if (editor instanceof LayoutEditor) { + mEditor = (LayoutEditor) editor; + } + + action.setEnabled((mTextSelection != null || mTreeSelection != null) + && mFile != null && mEditor != null); + } + + /** + * Create a new instance of our refactoring and a wizard to configure it. + */ + public abstract void run(IAction action); + + /** + * Returns the active editor (hopefully matching our selection) or null. + */ + private IEditorPart getActiveEditor() { + IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (wwin != null) { + IWorkbenchPage page = wwin.getActivePage(); + if (page != null) { + return page.getActiveEditor(); + } + } + + return null; + } + + /** + * Returns the active {@link IFile} (hopefully matching our selection) or null. + * The file is only returned if it's a file from a project with an Android nature. + * <p/> + * At that point we do not try to analyze if the selection nor the file is suitable + * for the refactoring. This check is performed when the refactoring is invoked since + * it can then produce meaningful error messages as needed. + */ + private IFile getSelectedFile(IEditorPart editor) { + if (editor != null) { + IEditorInput input = editor.getEditorInput(); + + if (input instanceof FileEditorInput) { + FileEditorInput fi = (FileEditorInput) input; + IFile file = fi.getFile(); + if (file.exists()) { + IProject proj = file.getProject(); + try { + if (proj != null && proj.hasNature(AdtConstants.NATURE_DEFAULT)) { + return file; + } + } catch (CoreException e) { + // ignore + } + } + } + } + + return null; + } + + public static IAction create(String title, LayoutEditor editor, + Class<? extends VisualRefactoringAction> clz) { + return new ActionWrapper(title, editor, clz); + } + + private static class ActionWrapper extends Action { + private Class<? extends VisualRefactoringAction> mClass; + private LayoutEditor mEditor; + + ActionWrapper(String title, LayoutEditor editor, + Class<? extends VisualRefactoringAction> clz) { + super(title); + mEditor = editor; + mClass = clz; + } + + @Override + public void run() { + VisualRefactoringAction action; + try { + action = mClass.newInstance(); + } catch (Exception e) { + AdtPlugin.log(e, null); + return; + } + action.init(mEditor.getEditorSite().getWorkbenchWindow()); + ISelection selection = mEditor.getEditorSite().getSelectionProvider().getSelection(); + action.selectionChanged(ActionWrapper.this, selection); + if (isEnabled()) { + action.run(ActionWrapper.this); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoringWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoringWizard.java new file mode 100644 index 0000000..2b6f25e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoringWizard.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.UserInputWizardPage; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; + +public abstract class VisualRefactoringWizard extends RefactoringWizard { + protected final LayoutEditor mEditor; + + public VisualRefactoringWizard(Refactoring refactoring, LayoutEditor editor) { + super(refactoring, DIALOG_BASED_USER_INTERFACE | PREVIEW_EXPAND_FIRST_NODE); + mEditor = editor; + } + + @Override + public boolean performFinish() { + mEditor.setIgnoreXmlUpdate(true); + try { + return super.performFinish(); + } finally { + mEditor.setIgnoreXmlUpdate(false); + mEditor.refreshXmlModel(); + } + } + + protected abstract static class VisualRefactoringInputPage extends UserInputWizardPage { + public VisualRefactoringInputPage(String name) { + super(name); + } + + /** + * Listener which can be attached on any widget in the wizard page to force + * modifications of the associated widget to validate the page again + */ + protected ModifyListener mModifyValidateListener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + validatePage(); + } + }; + + /** + * Listener which can be attached on any widget in the wizard page to force + * selection changes of the associated widget to validate the page again + */ + protected SelectionAdapter mSelectionValidateListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + validatePage(); + } + }; + + protected abstract boolean validatePage(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInAction.java new file mode 100644 index 0000000..1fd1837 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInAction.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ltk.ui.refactoring.RefactoringWizard; +import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; + +/** + * Action executed when the "Wrap In" menu item is invoked. + */ +public class WrapInAction extends VisualRefactoringAction { + @Override + public void run(IAction action) { + if ((mTextSelection != null || mTreeSelection != null) && mFile != null) { + WrapInRefactoring ref = new WrapInRefactoring(mFile, mEditor, + mTextSelection, mTreeSelection); + RefactoringWizard wizard = new WrapInWizard(ref, mEditor); + RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard); + try { + op.run(mWindow.getShell(), wizard.getDefaultPageTitle()); + } catch (InterruptedException e) { + // Interrupted. Pass. + } + } + } + + public static IAction create(LayoutEditor editor) { + return create("Wrap in Container...", editor, WrapInAction.class); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInContribution.java new file mode 100644 index 0000000..61d7987 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInContribution.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import org.eclipse.ltk.core.refactoring.RefactoringContribution; +import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; + +import java.util.Map; + +public class WrapInContribution extends RefactoringContribution { + + @SuppressWarnings("unchecked") + @Override + public RefactoringDescriptor createDescriptor(String id, String project, String description, + String comment, Map arguments, int flags) throws IllegalArgumentException { + return new WrapInRefactoring.Descriptor(project, description, comment, arguments); + } + + @SuppressWarnings("unchecked") + @Override + public Map retrieveArgumentMap(RefactoringDescriptor descriptor) { + if (descriptor instanceof WrapInRefactoring.Descriptor) { + return ((WrapInRefactoring.Descriptor) descriptor).getArguments(); + } + return super.retrieveArgumentMap(descriptor); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java new file mode 100644 index 0000000..7a3744b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; +import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; + +import com.android.annotations.VisibleForTesting; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.Refactoring; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.DeleteEdit; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Inserts a new layout surrounding the current selection, migrates namespace + * attributes (if wrapping the root node), and optionally migrates layout + * attributes and updates references elsewhere. + */ +@SuppressWarnings("restriction") // XML model +public class WrapInRefactoring extends VisualRefactoring { + private static final String KEY_ID = "name"; //$NON-NLS-1$ + private static final String KEY_TYPE = "type"; //$NON-NLS-1$ + + private String mId; + private String mTypeFqcn; + private String mInitializedAttributes; + + /** + * This constructor is solely used by {@link Descriptor}, + * to replay a previous refactoring. + * @param arguments argument map created by #createArgumentMap. + */ + WrapInRefactoring(Map<String, String> arguments) { + super(arguments); + mId = arguments.get(KEY_ID); + mTypeFqcn = arguments.get(KEY_TYPE); + } + + public WrapInRefactoring(IFile file, LayoutEditor editor, ITextSelection selection, + ITreeSelection treeSelection) { + super(file, editor, selection, treeSelection); + } + + @VisibleForTesting + WrapInRefactoring(List<Element> selectedElements, LayoutEditor editor) { + super(selectedElements, editor); + } + + @Override + public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, + OperationCanceledException { + RefactoringStatus status = new RefactoringStatus(); + + try { + pm.beginTask("Checking preconditions...", 6); + + if (mSelectionStart == -1 || mSelectionEnd == -1) { + status.addFatalError("No selection to wrap"); + return status; + } + + // Make sure the selection is contiguous + if (mTreeSelection != null) { + // TODO - don't do this if we based the selection on text. In this case, + // make sure we're -balanced-. + + List<CanvasViewInfo> infos = getSelectedViewInfos(); + if (!validateNotEmpty(infos, status)) { + return status; + } + + // Enforce that the selection is -contiguous- + if (!validateContiguous(infos, status)) { + return status; + } + } + + // Ensures that we have a valid DOM model: + if (mElements.size() == 0) { + status.addFatalError("Nothing to wrap"); + return status; + } + + pm.worked(1); + return status; + + } finally { + pm.done(); + } + } + + @Override + protected VisualRefactoringDescriptor createDescriptor() { + String comment = getName(); + return new Descriptor( + mProject.getName(), //project + comment, //description + comment, //comment + createArgumentMap()); + } + + @Override + protected Map<String, String> createArgumentMap() { + Map<String, String> args = super.createArgumentMap(); + args.put(KEY_TYPE, mTypeFqcn); + args.put(KEY_ID, mId); + + return args; + } + + @Override + public String getName() { + return "Wrap in Container"; + } + + void setId(String id) { + mId = id; + } + + void setType(String typeFqcn) { + mTypeFqcn = typeFqcn; + } + + void setInitializedAttributes(String initializedAttributes) { + mInitializedAttributes = initializedAttributes; + } + + @Override + protected List<Change> computeChanges(IProgressMonitor monitor) { + // (1) Insert the new container in front of the beginning of the + // first wrapped view + // (2) If the container is the new root, transfer namespace declarations + // to it + // (3) Insert the closing tag of the new container at the end of the + // last wrapped view + // (4) Reindent the wrapped views + // (5) If the user requested it, update all layout references to the + // wrapped views with the new container? + // For that matter, does RelativeLayout even require it? Probably not, + // it can point inside the current layout... + + // Add indent to all lines between mSelectionStart and mEnd + // TODO: Figure out the indentation amount? + // For now, use 4 spaces + String indentUnit = " "; //$NON-NLS-1$ + boolean separateAttributes = true; + IStructuredDocument document = mEditor.getStructuredDocument(); + String startIndent = AndroidXmlEditor.getIndentAtOffset(document, mSelectionStart); + + String viewClass = getViewClass(mTypeFqcn); + String androidNsPrefix = getAndroidNamespacePrefix(); + + + IFile file = mEditor.getInputFile(); + List<Change> changes = new ArrayList<Change>(); + TextFileChange change = new TextFileChange(file.getName(), file); + MultiTextEdit rootEdit = new MultiTextEdit(); + change.setEdit(rootEdit); + change.setTextType(EXT_XML); + + String id = ensureNewId(mId); + + // Update any layout references to the old id with the new id + if (id != null) { + String rootId = getRootId(); + IStructuredModel model = mEditor.getModelForRead(); + try { + IStructuredDocument doc = model.getStructuredDocument(); + if (doc != null) { + List<TextEdit> replaceIds = replaceIds(androidNsPrefix, + doc, mSelectionStart, mSelectionEnd, rootId, id); + for (TextEdit edit : replaceIds) { + rootEdit.addChild(edit); + } + } + } finally { + model.releaseFromRead(); + } + } + + // Insert namespace elements? + StringBuilder namespace = null; + List<DeleteEdit> deletions = new ArrayList<DeleteEdit>(); + Element primary = getPrimaryElement(); + if (primary != null && getDomDocument().getDocumentElement() == primary) { + namespace = new StringBuilder(); + + List<Attr> declarations = findNamespaceAttributes(primary); + for (Attr attribute : declarations) { + if (attribute instanceof IndexedRegion) { + // Delete the namespace declaration in the node which is no longer the root + IndexedRegion region = (IndexedRegion) attribute; + int startOffset = region.getStartOffset(); + int endOffset = region.getEndOffset(); + String text = getText(startOffset, endOffset); + DeleteEdit deletion = new DeleteEdit(startOffset, endOffset - startOffset); + deletions.add(deletion); + rootEdit.addChild(deletion); + text = text.trim(); + + // Insert the namespace declaration in the new root + if (separateAttributes) { + namespace.append('\n').append(startIndent).append(indentUnit); + } else { + namespace.append(' '); + } + namespace.append(text); + } + } + } + + // Insert begin tag: <type ...> + StringBuilder sb = new StringBuilder(); + sb.append('<'); + sb.append(viewClass); + + if (namespace != null) { + sb.append(namespace); + } + + // Set the ID if any + if (id != null) { + if (separateAttributes) { + sb.append('\n').append(startIndent).append(indentUnit); + } else { + sb.append(' '); + } + sb.append(androidNsPrefix).append(':'); + sb.append(ATTR_ID).append('=').append('"').append(id).append('"'); + } + + // If any of the elements are fill/match parent, use that instead + String width = VALUE_WRAP_CONTENT; + String height = VALUE_WRAP_CONTENT; + + for (Element element : getElements()) { + String oldWidth = element.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_WIDTH); + String oldHeight = element.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_HEIGHT); + + if (VALUE_MATCH_PARENT.equals(oldWidth) || VALUE_FILL_PARENT.equals(oldWidth)) { + width = oldWidth; + } + if (VALUE_MATCH_PARENT.equals(oldHeight) || VALUE_FILL_PARENT.equals(oldHeight)) { + height = oldHeight; + } + } + + // Add in width/height. + if (separateAttributes) { + sb.append('\n').append(startIndent).append(indentUnit); + } else { + sb.append(' '); + } + sb.append(androidNsPrefix).append(':'); + sb.append(ATTR_LAYOUT_WIDTH).append('=').append('"').append(width).append('"'); + + if (separateAttributes) { + sb.append('\n').append(startIndent).append(indentUnit); + } else { + sb.append(' '); + } + sb.append(androidNsPrefix).append(':'); + sb.append(ATTR_LAYOUT_HEIGHT).append('=').append('"').append(height).append('"'); + + if (mInitializedAttributes != null && mInitializedAttributes.length() > 0) { + for (String s : mInitializedAttributes.split(",")) { //$NON-NLS-1$ + sb.append(' '); + String[] nameValue = s.split("="); //$NON-NLS-1$ + String name = nameValue[0]; + String value = nameValue[1]; + if (name.startsWith(ANDROID_NS_NAME_PREFIX)) { + name = name.substring(ANDROID_NS_NAME_PREFIX.length()); + sb.append(androidNsPrefix).append(':'); + } + sb.append(name).append('=').append('"').append(value).append('"'); + } + } + + // Transfer layout_ attributes (other than width and height) + if (primary != null) { + List<Attr> layoutAttributes = findLayoutAttributes(primary); + for (Attr attribute : layoutAttributes) { + String name = attribute.getLocalName(); + if ((name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT)) + && ANDROID_URI.equals(attribute.getNamespaceURI())) { + // Already handled specially + continue; + } + + if (attribute instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) attribute; + int startOffset = region.getStartOffset(); + int endOffset = region.getEndOffset(); + String text = getText(startOffset, endOffset); + DeleteEdit deletion = new DeleteEdit(startOffset, endOffset - startOffset); + rootEdit.addChild(deletion); + deletions.add(deletion); + + if (separateAttributes) { + sb.append('\n').append(startIndent).append(indentUnit); + } else { + sb.append(' '); + } + sb.append(text.trim()); + } + } + } + + // Finish open tag: + sb.append('>'); + sb.append('\n').append(startIndent).append(indentUnit); + + InsertEdit beginEdit = new InsertEdit(mSelectionStart, sb.toString()); + rootEdit.addChild(beginEdit); + + String nested = getText(mSelectionStart, mSelectionEnd); + int index = 0; + while (index != -1) { + index = nested.indexOf('\n', index); + if (index != -1) { + index++; + InsertEdit newline = new InsertEdit(mSelectionStart + index, indentUnit); + // Some of the deleted namespaces may have had newlines - be careful + // not to overlap edits + boolean covered = false; + for (DeleteEdit deletion : deletions) { + if (deletion.covers(newline)) { + covered = true; + break; + } + } + if (!covered) { + rootEdit.addChild(newline); + } + } + } + + // Insert end tag: </type> + sb.setLength(0); + sb.append('\n').append(startIndent); + sb.append('<').append('/').append(viewClass).append('>'); + InsertEdit endEdit = new InsertEdit(mSelectionEnd, sb.toString()); + rootEdit.addChild(endEdit); + + changes.add(change); + return changes; + } + + String getOldType() { + Element primary = getPrimaryElement(); + if (primary != null) { + String oldType = primary.getTagName(); + if (oldType.indexOf('.') == -1) { + oldType = ANDROID_WIDGET_PREFIX + oldType; + } + return oldType; + } + + return null; + } + + @Override + VisualRefactoringWizard createWizard() { + return new WrapInWizard(this, mEditor); + } + + public static class Descriptor extends VisualRefactoringDescriptor { + public Descriptor(String project, String description, String comment, + Map<String, String> arguments) { + super("com.android.ide.eclipse.adt.refactoring.wrapin", //$NON-NLS-1$ + project, description, comment, arguments); + } + + @Override + protected Refactoring createRefactoring(Map<String, String> args) { + return new WrapInRefactoring(args); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInWizard.java new file mode 100644 index 0000000..69df9a1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInWizard.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.FQCN_GESTURE_OVERLAY_VIEW; +import static com.android.ide.common.layout.LayoutConstants.FQCN_LINEAR_LAYOUT; +import static com.android.ide.common.layout.LayoutConstants.FQCN_RADIO_BUTTON; +import static com.android.ide.common.layout.LayoutConstants.GESTURE_OVERLAY_VIEW; +import static com.android.ide.common.layout.LayoutConstants.RADIO_GROUP; +import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_INCLUDE; + +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CustomViewFinder; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.PaletteMetadataDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.resources.ResourceType; +import com.android.sdklib.IAndroidTarget; +import com.android.util.Pair; + +import org.eclipse.core.resources.IProject; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class WrapInWizard extends VisualRefactoringWizard { + private static final String SEPARATOR_LABEL = + "----------------------------------------"; //$NON-NLS-1$ + + public WrapInWizard(WrapInRefactoring ref, LayoutEditor editor) { + super(ref, editor); + setDefaultPageTitle("Wrap in Container"); + } + + @Override + protected void addUserInputPages() { + WrapInRefactoring ref = (WrapInRefactoring) getRefactoring(); + String oldType = ref.getOldType(); + addPage(new InputPage(mEditor.getProject(), oldType)); + } + + /** Wizard page which inputs parameters for the {@link WrapInRefactoring} operation */ + private static class InputPage extends VisualRefactoringInputPage { + private final IProject mProject; + private final String mOldType; + private Text mIdText; + private Combo mTypeCombo; + private List<Pair<String, ViewElementDescriptor>> mClassNames; + + public InputPage(IProject project, String oldType) { + super("WrapInInputPage"); //$NON-NLS-1$ + mProject = project; + mOldType = oldType; + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + Label typeLabel = new Label(composite, SWT.NONE); + typeLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + typeLabel.setText("Type of Container:"); + + mTypeCombo = new Combo(composite, SWT.READ_ONLY); + mTypeCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mTypeCombo.addSelectionListener(mSelectionValidateListener); + + Label idLabel = new Label(composite, SWT.NONE); + idLabel.setText("New Layout Id:"); + idLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + + mIdText = new Text(composite, SWT.BORDER); + mIdText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mIdText.addModifyListener(mModifyValidateListener); + + Set<String> exclude = Collections.singleton(VIEW_INCLUDE); + mClassNames = addLayouts(mProject, mOldType, mTypeCombo, exclude, true); + mTypeCombo.select(0); + + setControl(composite); + validatePage(); + + mTypeCombo.setFocus(); + } + + @Override + protected boolean validatePage() { + boolean ok = true; + + String id = mIdText.getText().trim(); + + if (id.length() == 0) { + setErrorMessage("ID required"); + ok = false; + } else { + // ...but if you do, it has to be valid! + ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, + ResourceType.ID); + String message = validator.isValid(id); + if (message != null) { + setErrorMessage(message); + ok = false; + } + } + + int selectionIndex = mTypeCombo.getSelectionIndex(); + String type = selectionIndex != -1 ? mClassNames.get(selectionIndex).getFirst() : null; + if (type == null) { + setErrorMessage("Select a container type"); + ok = false; // The user has chosen a separator + } + + if (ok) { + setErrorMessage(null); + + // Record state + WrapInRefactoring refactoring = + (WrapInRefactoring) getRefactoring(); + refactoring.setId(id); + refactoring.setType(type); + + ViewElementDescriptor descriptor = mClassNames.get(selectionIndex).getSecond(); + if (descriptor instanceof PaletteMetadataDescriptor) { + PaletteMetadataDescriptor paletteDescriptor = + (PaletteMetadataDescriptor) descriptor; + String initializedAttributes = paletteDescriptor.getInitializedAttributes(); + refactoring.setInitializedAttributes(initializedAttributes); + } else { + refactoring.setInitializedAttributes(null); + } + } + + setPageComplete(ok); + return ok; + } + } + + static List<Pair<String, ViewElementDescriptor>> addLayouts(IProject project, + String oldType, Combo combo, + Set<String> exclude, boolean addGestureOverlay) { + List<Pair<String, ViewElementDescriptor>> classNames = + new ArrayList<Pair<String, ViewElementDescriptor>>(); + + if (oldType != null && oldType.equals(FQCN_RADIO_BUTTON)) { + combo.add(RADIO_GROUP); + // NOT a fully qualified name since android widgets do not include the package + classNames.add(Pair.of(RADIO_GROUP, (ViewElementDescriptor) null)); + + combo.add(SEPARATOR_LABEL); + classNames.add(Pair.<String,ViewElementDescriptor>of(null, null)); + } + + Pair<List<String>,List<String>> result = CustomViewFinder.findViews(project, true); + List<String> customViews = result.getFirst(); + List<String> thirdPartyViews = result.getSecond(); + if (customViews.size() > 0) { + for (String view : customViews) { + combo.add(view); + classNames.add(Pair.of(view, (ViewElementDescriptor) null)); + } + combo.add(SEPARATOR_LABEL); + classNames.add(Pair.<String,ViewElementDescriptor>of(null, null)); + } + + // Populate type combo + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(project); + if (target != null) { + AndroidTargetData targetData = currentSdk.getTargetData(target); + if (targetData != null) { + ViewMetadataRepository repository = ViewMetadataRepository.get(); + List<Pair<String,List<ViewElementDescriptor>>> entries = + repository.getPaletteEntries(targetData, false, true); + // Find the layout category - it contains LinearLayout + List<ViewElementDescriptor> layoutDescriptors = null; + + search: for (Pair<String,List<ViewElementDescriptor>> pair : entries) { + List<ViewElementDescriptor> list = pair.getSecond(); + for (ViewElementDescriptor d : list) { + if (d.getFullClassName().equals(FQCN_LINEAR_LAYOUT)) { + // Found - use this list + layoutDescriptors = list; + break search; + } + } + } + if (layoutDescriptors != null) { + for (ViewElementDescriptor d : layoutDescriptors) { + String className = d.getFullClassName(); + if (exclude == null || !exclude.contains(className)) { + combo.add(d.getUiName()); + classNames.add(Pair.of(className, d)); + } + } + + // SWT does not support separators in combo boxes + combo.add(SEPARATOR_LABEL); + classNames.add(null); + + if (thirdPartyViews.size() > 0) { + for (String view : thirdPartyViews) { + combo.add(view); + classNames.add(Pair.of(view, (ViewElementDescriptor) null)); + } + combo.add(SEPARATOR_LABEL); + classNames.add(null); + } + + if (addGestureOverlay) { + combo.add(GESTURE_OVERLAY_VIEW); + classNames.add(Pair.<String, ViewElementDescriptor> of( + FQCN_GESTURE_OVERLAY_VIEW, null)); + + combo.add(SEPARATOR_LABEL); + classNames.add(Pair.<String,ViewElementDescriptor>of(null, null)); + } + } + + // Now add ALL known layout descriptors in case the user has + // a special case + layoutDescriptors = + targetData.getLayoutDescriptors().getLayoutDescriptors(); + + for (ViewElementDescriptor d : layoutDescriptors) { + String className = d.getFullClassName(); + if (exclude == null || !exclude.equals(className)) { + combo.add(d.getUiName()); + classNames.add(Pair.of(className, d)); + } + } + } + } + } else { + combo.add("SDK not initialized"); + classNames.add(Pair.<String,ViewElementDescriptor>of(null, null)); + } + + return classNames; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java index f4b10b7..60b5662 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java @@ -112,7 +112,7 @@ public class UiViewElementNode extends UiElementNode { layout_attrs.length); if (need_xmlns) { AttributeDescriptor desc = new XmlnsAttributeDescriptor( - LayoutConstants.ANDROID_NS_PREFIX, + LayoutConstants.ANDROID_NS_NAME, SdkConstants.NS_RESOURCES); mCachedAttributeDescriptors[direct_attrs.length + layout_attrs.length] = desc; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java index 349048f..1c92309 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java @@ -16,16 +16,18 @@ package com.android.ide.eclipse.adt.internal.editors.manifest; +import com.android.annotations.VisibleForTesting; import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; /** * Content Assist Processor for AndroidManifest.xml */ -final class ManifestContentAssist extends AndroidContentAssist { +@VisibleForTesting +public final class ManifestContentAssist extends AndroidContentAssist { /** - * Constructor for ManifestContentAssist + * Constructor for ManifestContentAssist */ public ManifestContentAssist() { super(AndroidTargetData.DESCRIPTOR_MANIFEST); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java index fb742a7..38a5e6b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.manifest; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; @@ -54,9 +54,10 @@ import javax.xml.xpath.XPathExpressionException; /** * Multi-page form editor for AndroidManifest.xml. */ +@SuppressWarnings("restriction") public final class ManifestEditor extends AndroidXmlEditor { - public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".manifest.ManifestEditor"; //$NON-NLS-1$ + public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".manifest.ManifestEditor"; //$NON-NLS-1$ private final static String EMPTY = ""; //$NON-NLS-1$ @@ -197,7 +198,6 @@ public final class ManifestEditor extends AndroidXmlEditor { return null; } - @SuppressWarnings("restriction") private void onDescriptorsChanged() { IStructuredModel model = getModelForRead(); if (model != null) { @@ -256,7 +256,7 @@ public final class ManifestEditor extends AndroidXmlEditor { try { // get the markers for the file IMarker[] markers = inputFile.findMarkers( - AndroidConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO); + AdtConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO); AndroidManifestDescriptors desc = getManifestDescriptors(); if (desc != null) { @@ -304,12 +304,12 @@ public final class ManifestEditor extends AndroidXmlEditor { */ private void processMarker(IMarker marker, List<UiElementNode> nodeList, int kind) { // get the data from the marker - String nodeType = marker.getAttribute(AndroidConstants.MARKER_ATTR_TYPE, EMPTY); + String nodeType = marker.getAttribute(AdtConstants.MARKER_ATTR_TYPE, EMPTY); if (nodeType == EMPTY) { return; } - String className = marker.getAttribute(AndroidConstants.MARKER_ATTR_CLASS, EMPTY); + String className = marker.getAttribute(AdtConstants.MARKER_ATTR_CLASS, EMPTY); if (className == EMPTY) { return; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java new file mode 100644 index 0000000..0feab55 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java @@ -0,0 +1,559 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.manifest; + +import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE; +import static com.android.sdklib.SdkConstants.NS_RESOURCES; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_ICON; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_LABEL; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_NAME; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_PACKAGE; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION; +import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_THEME; +import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY; +import static com.android.sdklib.xml.AndroidManifest.NODE_USES_SDK; +import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.io.IFolderWrapper; +import com.android.io.IAbstractFile; +import com.android.resources.ScreenSize; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.xml.AndroidManifest; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchParticipant; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.SearchRequestor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.editors.text.TextFileDocumentProvider; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +/** + * Retrieves and caches manifest information such as the themes to be used for + * a given activity. + * + * @see AndroidManifest + */ +public class ManifestInfo { + /** + * The maximum number of milliseconds to search for an activity in the codebase when + * attempting to associate layouts with activities in + * {@link #guessActivity(IFile, String)} + */ + private static final int SEARCH_TIMEOUT_MS = 3000; + + private final IProject mProject; + private String mPackage; + private String mManifestTheme; + private Map<String, String> mActivityThemes; + private IAbstractFile mManifestFile; + private long mLastModified; + private int mTargetSdk; + private String mApplicationIcon; + private String mApplicationLabel; + + /** + * Qualified name for the per-project non-persistent property storing the + * {@link ManifestInfo} for this project + */ + final static QualifiedName MANIFEST_FINDER = new QualifiedName(AdtPlugin.PLUGIN_ID, + "manifest"); //$NON-NLS-1$ + + /** + * Constructs an {@link ManifestInfo} for the given project. Don't use this method; + * use the {@link #get} factory method instead. + * + * @param project project to create an {@link ManifestInfo} for + */ + private ManifestInfo(IProject project) { + mProject = project; + } + + /** + * Returns the {@link ManifestInfo} for the given project + * + * @param project the project the finder is associated with + * @return a {@ManifestInfo} for the given project, never null + */ + public static ManifestInfo get(IProject project) { + ManifestInfo finder = null; + try { + finder = (ManifestInfo) project.getSessionProperty(MANIFEST_FINDER); + } catch (CoreException e) { + // Not a problem; we will just create a new one + } + + if (finder == null) { + finder = new ManifestInfo(project); + try { + project.setSessionProperty(MANIFEST_FINDER, finder); + } catch (CoreException e) { + AdtPlugin.log(e, "Can't store ManifestInfo"); + } + } + + return finder; + } + + /** + * Ensure that the package, theme and activity maps are initialized and up to date + * with respect to the manifest file + */ + private void sync() { + if (mManifestFile == null) { + IFolderWrapper projectFolder = new IFolderWrapper(mProject); + mManifestFile = AndroidManifest.getManifest(projectFolder); + if (mManifestFile == null) { + return; + } + } + + // Check to see if our data is up to date + long fileModified = mManifestFile.getModificationStamp(); + if (fileModified == mLastModified) { + // Already have up to date data + return; + } + mLastModified = fileModified; + + mActivityThemes = new HashMap<String, String>(); + mManifestTheme = null; + mTargetSdk = 1; // Default when not specified + mPackage = ""; //$NON-NLS-1$ + mApplicationIcon = null; + mApplicationLabel = null; + + Document document = null; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + InputSource is = new InputSource(mManifestFile.getContents()); + + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + document = builder.parse(is); + + Element root = document.getDocumentElement(); + mPackage = root.getAttribute(ATTRIBUTE_PACKAGE); + NodeList activities = document.getElementsByTagName(NODE_ACTIVITY); + for (int i = 0, n = activities.getLength(); i < n; i++) { + Element activity = (Element) activities.item(i); + String theme = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME); + if (theme != null && theme.length() > 0) { + String name = activity.getAttributeNS(NS_RESOURCES, ATTRIBUTE_NAME); + if (name.startsWith(".") //$NON-NLS-1$ + && mPackage != null && mPackage.length() > 0) { + name = mPackage + name; + } + mActivityThemes.put(name, theme); + } + } + + NodeList applications = root.getElementsByTagName(AndroidManifest.NODE_APPLICATION); + if (applications.getLength() > 0) { + assert applications.getLength() == 1; + Element application = (Element) applications.item(0); + if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON)) { + mApplicationIcon = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_ICON); + } + if (application.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL)) { + mApplicationLabel = application.getAttributeNS(NS_RESOURCES, ATTRIBUTE_LABEL); + } + } + + // Look up target SDK + String defaultTheme = root.getAttributeNS(NS_RESOURCES, ATTRIBUTE_THEME); + if (defaultTheme == null || defaultTheme.length() == 0) { + // From manifest theme documentation: + // "If that attribute is also not set, the default system theme is used." + + NodeList usesSdks = root.getElementsByTagName(NODE_USES_SDK); + if (usesSdks.getLength() > 0) { + Element usesSdk = (Element) usesSdks.item(0); + String targetSdk = null; + if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_TARGET_SDK_VERSION)) { + targetSdk = usesSdk.getAttributeNS(NS_RESOURCES, + ATTRIBUTE_TARGET_SDK_VERSION); + } else if (usesSdk.hasAttributeNS(NS_RESOURCES, ATTRIBUTE_MIN_SDK_VERSION)) { + targetSdk = usesSdk.getAttributeNS(NS_RESOURCES, + ATTRIBUTE_MIN_SDK_VERSION); + } + if (targetSdk != null) { + int apiLevel = -1; + try { + apiLevel = Integer.valueOf(targetSdk); + } catch (NumberFormatException e) { + // Handle codename + if (Sdk.getCurrent() != null) { + IAndroidTarget target = Sdk.getCurrent().getTargetFromHashString( + "android-" + targetSdk); //$NON-NLS-1$ + if (target != null) { + // codename future API level is current api + 1 + apiLevel = target.getVersion().getApiLevel() + 1; + } + } + } + + mTargetSdk = apiLevel; + } + } + } else { + mManifestTheme = defaultTheme; + } + } catch (SAXException e) { + AdtPlugin.log(e, "Malformed manifest"); + } catch (Exception e) { + AdtPlugin.log(e, "Could not read Manifest data"); + } + } + + /** + * Returns the default package registered in the Android manifest + * + * @return the default package registered in the manifest + */ + public String getPackage() { + sync(); + return mPackage; + } + + /** + * Returns a map from activity full class names to the corresponding theme style to be + * used + * + * @return a map from activity fqcn to theme style + */ + public Map<String, String> getActivityThemes() { + sync(); + return mActivityThemes; + } + + /** + * Returns the default theme for this project, by looking at the manifest default + * theme registration, target SDK, rendering target, etc. + * + * @param renderingTarget the rendering target use to render the theme, or null + * @param screenSize the screen size to obtain a default theme for, or null if unknown + * @return the theme to use for this project, never null + */ + public String getDefaultTheme(IAndroidTarget renderingTarget, ScreenSize screenSize) { + sync(); + + if (mManifestTheme != null) { + return mManifestTheme; + } + + int renderingTargetSdk = mTargetSdk; + if (renderingTarget != null) { + renderingTargetSdk = renderingTarget.getVersion().getApiLevel(); + } + + int apiLevel = Math.min(mTargetSdk, renderingTargetSdk); + // For now this theme works only on XLARGE screens. When it works for all sizes, + // add that new apiLevel to this check. + if (apiLevel >= 11 && screenSize == ScreenSize.XLARGE) { + return PREFIX_ANDROID_STYLE + "Theme.Holo"; //$NON-NLS-1$ + } else { + return PREFIX_ANDROID_STYLE + "Theme"; //$NON-NLS-1$ + } + } + + /** + * Returns the application icon, or null + * + * @return the application icon, or null + */ + public String getApplicationIcon() { + sync(); + return mApplicationIcon; + } + + /** + * Returns the application label, or null + * + * @return the application label, or null + */ + public String getApplicationLabel() { + sync(); + return mApplicationLabel; + } + + /** + * Returns the activity associated with the given layout file. Makes an educated guess + * by peeking at the usages of the R.layout.name field corresponding to the layout and + * if it finds a usage. + * + * @param project the project containing the layout + * @param layoutName the layout whose activity we want to look up + * @param pkg the package containing activities + * @return the activity name + */ + public static String guessActivity(IProject project, String layoutName, String pkg) { + final AtomicReference<String> activity = new AtomicReference<String>(); + SearchRequestor requestor = new SearchRequestor() { + @Override + public void acceptSearchMatch(SearchMatch match) throws CoreException { + Object element = match.getElement(); + if (element instanceof IMethod) { + IMethod method = (IMethod) element; + IType declaringType = method.getDeclaringType(); + String fqcn = declaringType.getFullyQualifiedName(); + if (activity.get() == null + || declaringType.getSuperclassName().endsWith("Activity") //$NON-NLS-1$ + || method.getElementName().equals("onCreate")) { //$NON-NLS-1$ + activity.set(fqcn); + } + } + } + }; + try { + IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); + if (javaProject == null) { + return null; + } + // TODO - look around a bit more and see if we can figure out whether the + // call if from within a setContentView call! + + // Search for which java classes call setContentView(R.layout.layoutname); + String typeFqcn = "R.layout"; //$NON-NLS-1$ + if (pkg != null) { + typeFqcn = pkg + '.' + typeFqcn; + } + + IType type = javaProject.findType(typeFqcn); + if (type != null) { + IField field = type.getField(layoutName); + if (field.exists()) { + SearchPattern pattern = SearchPattern.createPattern(field, REFERENCES); + search(requestor, javaProject, pattern); + } + } + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + return activity.get(); + } + + /** + * Returns the activity associated with the given layout file. + * <p> + * This is an alternative to {@link #guessActivity(IFile, String)}. Whereas + * guessActivity simply looks for references to "R.layout.foo", this method searches + * for all usages of Activity#setContentView(int), and for each match it looks up the + * corresponding call text (such as "setContentView(R.layout.foo)"). From this it uses + * a regexp to pull out "foo" from this, and stores the association that layout "foo" + * is associated with the activity class that contained the setContentView call. + * <p> + * This has two potential advantages: + * <ol> + * <li>It can be faster. We do the reference search -once-, and we've built a map of + * all the layout-to-activity mappings which we can then immediately look up other + * layouts for, which is particularly useful at startup when we have to compute the + * layout activity associations to populate the theme choosers. + * <li>It can be more accurate. Just because an activity references an "R.layout.foo" + * field doesn't mean it's setting it as a content view. + * </ol> + * However, this second advantage is also its chief problem. There are some common + * code constructs which means that the associated layout is not explicitly referenced + * in a direct setContentView call; on a couple of sample projects I tested I found + * patterns like for example "setContentView(v)" where "v" had been computed earlier. + * Therefore, for now we're going to stick with the more general approach of just + * looking up each field when needed. We're keeping the code around, though statically + * compiled out with the "if (false)" construct below in case we revisit this. + * + * @param layoutFile the layout whose activity we want to look up + * @return the activity name + */ + @SuppressWarnings("all") + public String guessActivityBySetContentView(String layoutName) { + if (false) { + // These should be fields + final Pattern LAYOUT_FIELD_PATTERN = + Pattern.compile("R\\.layout\\.([a-z0-9_]+)"); //$NON-NLS-1$ + Map<String, String> mUsages = null; + + sync(); + if (mUsages == null) { + final Map<String, String> usages = new HashMap<String, String>(); + mUsages = usages; + SearchRequestor requestor = new SearchRequestor() { + @Override + public void acceptSearchMatch(SearchMatch match) throws CoreException { + Object element = match.getElement(); + if (element instanceof IMethod) { + IMethod method = (IMethod) element; + IType declaringType = method.getDeclaringType(); + String fqcn = declaringType.getFullyQualifiedName(); + IDocumentProvider provider = new TextFileDocumentProvider(); + IResource resource = match.getResource(); + try { + provider.connect(resource); + IDocument document = provider.getDocument(resource); + if (document != null) { + String matchText = document.get(match.getOffset(), + match.getLength()); + Matcher matcher = LAYOUT_FIELD_PATTERN.matcher(matchText); + if (matcher.find()) { + usages.put(matcher.group(1), fqcn); + } + } + } catch (Exception e) { + AdtPlugin.log(e, "Can't find range information for %1$s", + resource.getName()); + } finally { + provider.disconnect(resource); + } + } + } + }; + try { + IJavaProject javaProject = BaseProjectHelper.getJavaProject(mProject); + if (javaProject == null) { + return null; + } + + // Search for which java classes call setContentView(R.layout.layoutname); + String typeFqcn = "R.layout"; //$NON-NLS-1$ + if (mPackage != null) { + typeFqcn = mPackage + '.' + typeFqcn; + } + + IType activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY); + if (activityType != null) { + IMethod method = activityType.getMethod( + "setContentView", new String[] {"I"}); //$NON-NLS-1$ //$NON-NLS-2$ + if (method.exists()) { + SearchPattern pattern = SearchPattern.createPattern(method, + REFERENCES); + search(requestor, javaProject, pattern); + } + } + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + } + + return mUsages.get(layoutName); + } + + return null; + } + + /** + * Performs a search using the given pattern, scope and handler. The search will abort + * if it takes longer than {@link #SEARCH_TIMEOUT_MS} milliseconds. + */ + private static void search(SearchRequestor requestor, IJavaProject javaProject, + SearchPattern pattern) throws CoreException { + // Find the package fragment specified in the manifest; the activities should + // live there. + IJavaSearchScope scope = createPackageScope(javaProject); + + SearchParticipant[] participants = new SearchParticipant[] { + SearchEngine.getDefaultSearchParticipant() + }; + SearchEngine engine = new SearchEngine(); + + final long searchStart = System.currentTimeMillis(); + NullProgressMonitor monitor = new NullProgressMonitor() { + private boolean mCancelled; + @Override + public void internalWorked(double work) { + long searchEnd = System.currentTimeMillis(); + if (searchEnd - searchStart > SEARCH_TIMEOUT_MS) { + mCancelled = true; + } + } + + @Override + public boolean isCanceled() { + return mCancelled; + } + }; + engine.search(pattern, participants, scope, requestor, monitor); + } + + /** Creates a package search scope for the first package root in the given java project */ + private static IJavaSearchScope createPackageScope(IJavaProject javaProject) { + IPackageFragmentRoot packageRoot = getSourcePackageRoot(javaProject); + + IJavaSearchScope scope; + if (packageRoot != null) { + IJavaElement[] scopeElements = new IJavaElement[] { packageRoot }; + scope = SearchEngine.createJavaSearchScope(scopeElements); + } else { + scope = SearchEngine.createWorkspaceScope();; + } + return scope; + } + + /** Returns the first package root for the given java project */ + private static IPackageFragmentRoot getSourcePackageRoot(IJavaProject javaProject) { + IPackageFragmentRoot packageRoot = null; + List<IPath> sources = BaseProjectHelper.getSourceClasspaths(javaProject); + + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + for (IPath path : sources) { + IResource firstSource = workspace.getRoot().findMember(path); + if (firstSource != null) { + packageRoot = javaProject.getPackageFragmentRoot(firstSource); + if (packageRoot != null) { + break; + } + } + } + return packageRoot; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java index 7bc3692..c07b7be 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/descriptors/AndroidManifestDescriptors.java @@ -434,7 +434,7 @@ public final class AndroidManifestDescriptors implements IDescriptorProvider { * <p/> * Capitalizes the first letter and replace non-alphabet by a space followed by a capital. */ - private String getUiName(String xmlName) { + private static String getUiName(String xmlName) { StringBuilder sb = new StringBuilder(); boolean capitalize = true; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java index 348e0a3..7b1f4fa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors.manifest.model; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; @@ -394,7 +394,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode { if (className.startsWith(".")) { //$NON-NLS-1$ fullClassName = packageName + className; } else { - String[] segments = className.split(AndroidConstants.RE_DOT); + String[] segments = className.split(AdtConstants.RE_DOT); if (segments.length == 1) { fullClassName = packageName + "." + className; //$NON-NLS-1$ } @@ -503,7 +503,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode { // look for how many segments we have left. // if one, just write it that way. // if more than one, write it with a leading dot. - String[] packages = name.split(AndroidConstants.RE_DOT); + String[] packages = name.split(AdtConstants.RE_DOT); if (packages.length == 1) { text.setText(name); } else { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java index 0b78a22..a4f701f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiManifestElementNode.java @@ -56,7 +56,7 @@ public final class UiManifestElementNode extends UiElementNode { /** * Computes a short string describing the UI node suitable for tree views. * Uses the element's attribute "android:name" if present, or the "android:label" one - * followed by the element's name. + * followed by the element's name if not repeated. * * @return A short string describing the UI node suitable for tree views. */ @@ -68,12 +68,13 @@ public final class UiManifestElementNode extends UiElementNode { manifestDescriptors = target.getManifestDescriptors(); } + String name = getDescriptor().getUiName(); + if (manifestDescriptors != null && getXmlNode() != null && getXmlNode() instanceof Element && getXmlNode().hasAttributes()) { - // Application and Manifest nodes have a special treatment: they are unique nodes // so we don't bother trying to differentiate their strings and we fall back to // just using the UI name below. @@ -90,12 +91,18 @@ public final class UiManifestElementNode extends UiElementNode { AndroidManifestDescriptors.ANDROID_LABEL_ATTR); } if (attr != null && attr.length() > 0) { - return String.format("%1$s (%2$s)", attr, getDescriptor().getUiName()); + // If the ui name is repeated in the attribute value, don't use it. + // Typical case is to avoid ".pkg.MyActivity (Activity)". + if (attr.contains(name)) { + return attr; + } else { + return String.format("%1$s (%2$s)", attr, name); + } } } } - return String.format("%1$s", getDescriptor().getUiName()); + return String.format("%1$s", name); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java index 2386a8c..f821375 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewLinksPart.java @@ -17,6 +17,8 @@ package com.android.ide.eclipse.adt.internal.editors.manifest.pages; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor; import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; @@ -44,7 +46,7 @@ final class OverviewLinksPart extends ManifestSectionPart { section.setDescription("The content of the Android Manifest is made up of three sections. You can also edit the XML directly."); Composite table = createTableLayout(toolkit, 2 /* numColumns */); - + StringBuffer buf = new StringBuffer(); buf.append(String.format("<form><li style=\"image\" value=\"app_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$ ApplicationPage.PAGE_ID)); @@ -67,7 +69,7 @@ final class OverviewLinksPart extends ManifestSectionPart { buf.append(": Instrumentation defined."); buf.append("</li>"); //$NON-NLS-1$ - buf.append(String.format("<li style=\"image\" value=\"android_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$ + buf.append(String.format("<li style=\"image\" value=\"srce_img\"><a href=\"page:%1$s\">", //$NON-NLS-1$ ManifestEditor.TEXT_EDITOR_ID)); buf.append("XML Source"); buf.append("</a>"); //$NON-NLS-1$ @@ -81,12 +83,13 @@ final class OverviewLinksPart extends ManifestSectionPart { mFormText = createFormText(table, toolkit, true, buf.toString(), false /* setupLayoutData */); - + AndroidManifestDescriptors manifestDescriptor = editor.getManifestDescriptors(); Image androidLogo = AdtPlugin.getAndroidLogo(); mFormText.setImage("android_img", androidLogo); //$NON-NLS-1$ - + mFormText.setImage("srce_img", IconFactory.getInstance().getIcon(AndroidXmlEditor.ICON_XML_PAGE)); + if (manifestDescriptor != null) { mFormText.setImage("app_img", getIcon(manifestDescriptor.getApplicationElement())); //$NON-NLS-1$ mFormText.setImage("perm_img", getIcon(manifestDescriptor.getPermissionElement())); //$NON-NLS-1$ @@ -98,7 +101,7 @@ final class OverviewLinksPart extends ManifestSectionPart { } mFormText.addHyperlinkListener(editor.createHyperlinkListener()); } - + /** * Update the UI with information from the new descriptors. * <p/>At this point, this only refreshes the icons. @@ -114,12 +117,8 @@ final class OverviewLinksPart extends ManifestSectionPart { mFormText.setImage("inst_img", getIcon(manifestDescriptor.getInstrumentationElement())); //$NON-NLS-1$ } } - + private Image getIcon(ElementDescriptor desc) { - if (desc != null && desc.getIcon() != null) { - return desc.getIcon(); - } - - return AdtPlugin.getAndroidLogo(); + return desc.getCustomizedIcon(); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java index 4ddb1e9..911a833 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/MenuEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.menu; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory; @@ -42,7 +42,7 @@ import javax.xml.xpath.XPathExpressionException; */ public class MenuEditor extends AndroidXmlEditor { - public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".menu.MenuEditor"; //$NON-NLS-1$ + public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".menu.MenuEditor"; //$NON-NLS-1$ /** Root node of the UI element hierarchy */ private UiElementNode mUiRootNode; @@ -104,6 +104,8 @@ public class MenuEditor extends AndroidXmlEditor { } } + private boolean mUpdatingModel; + /** * Processes the new XML Model, which XML root node is given. * @@ -111,33 +113,43 @@ public class MenuEditor extends AndroidXmlEditor { */ @Override protected void xmlModelChanged(Document xml_doc) { - // init the ui root on demand - initUiRootNode(false /*force*/); - - mUiRootNode.setXmlDocument(xml_doc); - if (xml_doc != null) { - ElementDescriptor root_desc = mUiRootNode.getDescriptor(); - try { - XPath xpath = AndroidXPathFactory.newXPath(); - Node node = (Node) xpath.evaluate("/" + root_desc.getXmlName(), //$NON-NLS-1$ - xml_doc, - XPathConstants.NODE); - if (node == null && root_desc.getMandatory() != Mandatory.NOT_MANDATORY) { - // Create the root element if it doesn't exist yet (for empty new documents) - node = mUiRootNode.createXmlNode(); - } - - // Refresh the manifest UI node and all its descendants - mUiRootNode.loadFromXmlNode(node); + if (mUpdatingModel) { + return; + } - // TODO ? startMonitoringMarkers(); - } catch (XPathExpressionException e) { - AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ - root_desc.getXmlName()); + try { + mUpdatingModel = true; + + // init the ui root on demand + initUiRootNode(false /*force*/); + + mUiRootNode.setXmlDocument(xml_doc); + if (xml_doc != null) { + ElementDescriptor root_desc = mUiRootNode.getDescriptor(); + try { + XPath xpath = AndroidXPathFactory.newXPath(); + Node node = (Node) xpath.evaluate("/" + root_desc.getXmlName(), //$NON-NLS-1$ + xml_doc, + XPathConstants.NODE); + if (node == null && root_desc.getMandatory() != Mandatory.NOT_MANDATORY) { + // Create the root element if it doesn't exist yet (for empty new documents) + node = mUiRootNode.createXmlNode(); + } + + // Refresh the manifest UI node and all its descendants + mUiRootNode.loadFromXmlNode(node); + + // TODO ? startMonitoringMarkers(); + } catch (XPathExpressionException e) { + AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ + root_desc.getXmlName()); + } } - } - super.xmlModelChanged(xml_doc); + super.xmlModelChanged(xml_doc); + } finally { + mUpdatingModel = false; + } } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/descriptors/MenuDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/descriptors/MenuDescriptors.java index a84c743..f093a9b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/descriptors/MenuDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/menu/descriptors/MenuDescriptors.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.menu.descriptors; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; import com.android.ide.common.resources.platform.DeclareStyleableInfo; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; @@ -122,7 +122,7 @@ public final class MenuDescriptors implements IDescriptorProvider { new ElementDescriptor[] { top_item }, // childrenElements, false /* mandatory */); - XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_PREFIX, + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor(ANDROID_NS_NAME, SdkConstants.NS_RESOURCES); updateElement(mDescriptor, styleMap, "Menu", xmlns); //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesContentAssist.java index d8b497f..1661100 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesContentAssist.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesContentAssist.java @@ -16,18 +16,187 @@ package com.android.ide.eclipse.adt.internal.editors.resources; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME_PREFIX; +import static com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor.ATTRIBUTE_ICON_FILENAME; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ITEM_TAG; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.STYLE_ELEMENT; +import static com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData.DESCRIPTOR_LAYOUT; + +import com.android.annotations.VisibleForTesting; import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.descriptors.SeparatorAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import org.eclipse.jface.text.contentassist.CompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Content Assist Processor for /res/values and /res/drawable XML files + * <p> + * Further enhancements: + * <ul> + * <li> Complete prefixes in the style element itself for the name attribute + * <li> Complete parent names + * </ul> */ -class ResourcesContentAssist extends AndroidContentAssist { +@VisibleForTesting +public class ResourcesContentAssist extends AndroidContentAssist { /** - * Constructor for ResourcesContentAssist + * Constructor for ResourcesContentAssist */ public ResourcesContentAssist() { super(AndroidTargetData.DESCRIPTOR_RESOURCES); } + + @Override + protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset, + String parentTagName, String attributeName, Node node, String wordPrefix, + boolean skipEndTag, int replaceLength) { + super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node, + wordPrefix, skipEndTag, replaceLength); + + if (parentTagName.equals(ITEM_TAG) && NAME_ATTR.equals(attributeName)) { + + // Special case: the user is code completing inside + // <style><item name="^"/></style> + // In this case, ALL attributes are valid so we need to synthesize + // a choice list from all the layout descriptors + + // Add in android: as a completion item? + if (startsWith(ANDROID_NS_NAME_PREFIX, wordPrefix)) { + proposals.add(new CompletionProposal(ANDROID_NS_NAME_PREFIX, + offset - wordPrefix.length(), // replacementOffset + wordPrefix.length() + replaceLength, // replacementLength + ANDROID_NS_NAME_PREFIX.length(), // cursorPosition + IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME), + null, null, null)); + } + + + String attributePrefix = wordPrefix; + if (startsWith(attributePrefix, ANDROID_NS_NAME_PREFIX)) { + attributePrefix = attributePrefix.substring(ANDROID_NS_NAME_PREFIX.length()); + } + + AndroidTargetData data = mEditor.getTargetData(); + if (data != null) { + IDescriptorProvider descriptorProvider = + data.getDescriptorProvider( + AndroidTargetData.DESCRIPTOR_LAYOUT); + if (descriptorProvider != null) { + ElementDescriptor[] rootElementDescriptors = + descriptorProvider.getRootElementDescriptors(); + Map<String, AttributeDescriptor> matches = + new HashMap<String, AttributeDescriptor>(180); + for (ElementDescriptor elementDesc : rootElementDescriptors) { + for (AttributeDescriptor desc : elementDesc.getAttributes()) { + if (desc instanceof SeparatorAttributeDescriptor) { + continue; + } + String name = desc.getXmlLocalName(); + if (startsWith(name, attributePrefix)) { + matches.put(name, desc); + } + } + } + + List<AttributeDescriptor> sorted = + new ArrayList<AttributeDescriptor>(matches.size()); + sorted.addAll(matches.values()); + Collections.sort(sorted); + char needTag = 0; + addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix, + needTag, true /* isAttribute */, false /* isNew */, + skipEndTag /* skipEndTag */, replaceLength); + return; + } + } + } + } + + @Override + protected void computeTextValues(List<ICompletionProposal> proposals, int offset, + Node parentNode, Node currentNode, UiElementNode uiParent, + String prefix) { + super.computeTextValues(proposals, offset, parentNode, currentNode, uiParent, + prefix); + + if (parentNode.getNodeName().equals(ITEM_TAG) && + parentNode.getParentNode() != null && + STYLE_ELEMENT.equals(parentNode.getParentNode().getNodeName())) { + + // Special case: the user is code completing inside + // <style><item name="android:foo"/>|</style> + // In this case, we need to find the right AttributeDescriptor + // for the given attribute and offer its values + + AndroidTargetData data = mEditor.getTargetData(); + if (data != null) { + IDescriptorProvider descriptorProvider = + data.getDescriptorProvider(DESCRIPTOR_LAYOUT); + if (descriptorProvider != null) { + + Element element = (Element) parentNode; + String attrName = element.getAttribute(NAME_ATTR); + int pos = attrName.indexOf(':'); + if (pos >= 0) { + attrName = attrName.substring(pos + 1); + } + + // Search for an attribute match + ElementDescriptor[] rootElementDescriptors = + descriptorProvider.getRootElementDescriptors(); + for (ElementDescriptor elementDesc : rootElementDescriptors) { + for (AttributeDescriptor desc : elementDesc.getAttributes()) { + if (desc.getXmlLocalName().equals(attrName)) { + // Make a ui parent node such that we can attach our + // newfound attribute node to something (the code we delegate + // to for looking up attribute completions will look at the + // parent node and ask for its editor etc.) + if (uiParent == null) { + DocumentDescriptor documentDescriptor = + data.getLayoutDescriptors().getDescriptor(); + uiParent = documentDescriptor.createUiNode(); + uiParent.setEditor(mEditor); + } + + UiAttributeNode currAttrNode = desc.createUiNode(uiParent); + AttribInfo attrInfo = new AttribInfo(); + Object[] values = getAttributeValueChoices(currAttrNode, attrInfo, + prefix); + char needTag = attrInfo.needTag; + if (attrInfo.correctedPrefix != null) { + prefix = attrInfo.correctedPrefix; + } + boolean isAttribute = true; + boolean isNew = false; + int replaceLength = computeTextReplaceLength(currentNode, offset); + addMatchingProposals(proposals, values, offset, currentNode, + prefix, needTag, isAttribute, isNew, + false /* skipEndTag */, replaceLength); + return; + } + } + } + } + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesEditor.java index 79d11ed..83e2d8c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.resources; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; @@ -38,11 +38,11 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; /** - * Multi-page form editor for /res/values and /res/drawable XML files. + * Multi-page form editor for /res/values XML files. */ public class ResourcesEditor extends AndroidXmlEditor { - public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".resources.ResourcesEditor"; //$NON-NLS-1$ + public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".resources.ResourcesEditor"; //$NON-NLS-1$ /** Root node of the UI element hierarchy */ private UiElementNode mUiResourcesNode; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesTreePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesTreePage.java index 2c383b3..f352cfa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesTreePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/ResourcesTreePage.java @@ -16,12 +16,12 @@ package com.android.ide.eclipse.adt.internal.editors.resources; +import com.android.ide.common.resources.ResourceFolder; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiTreeBlock; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import org.eclipse.core.resources.IFile; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java index 721e093..6a2368a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/resources/descriptors/ResourcesDescriptors.java @@ -35,12 +35,14 @@ public final class ResourcesDescriptors implements IDescriptorProvider { // Public attributes names, attributes descriptors and elements descriptors - public static final String ROOT_ELEMENT = "resources"; //$NON-NLS-1$ + public static final String ROOT_ELEMENT = "resources"; //$NON-NLS-1$ public static final String STRING_ELEMENT = "string"; //$NON-NLS-1$ + public static final String STYLE_ELEMENT = "style"; //$NON-NLS-1$ - public static final String ITEM_TAG = "item"; //$NON-NLS-1$ + public static final String ITEM_TAG = "item"; //$NON-NLS-1$ public static final String NAME_ATTR = "name"; //$NON-NLS-1$ public static final String TYPE_ATTR = "type"; //$NON-NLS-1$ + public static final String PARENT_ATTR = "parent"; //$NON-NLS-1$ private static final ResourcesDescriptors sThis = new ResourcesDescriptors(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/DecorComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/DecorComposite.java index 96539fa..4edb09f 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/DecorComposite.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/DecorComposite.java @@ -74,6 +74,8 @@ public class DecorComposite extends Composite { setImage(i); } + content.createToolbarItems(mToolbar); + return this; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/IDecorContent.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/IDecorContent.java index eb06a1b..6f14165 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/IDecorContent.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/IDecorContent.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.ui; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.ToolBar; /** * Describes the content of a {@link IDecorContent}. @@ -32,6 +33,16 @@ public interface IDecorContent { public void createControl(Composite parent); /** + * Creates the toolbar items that will be displayed in the {@link IDecorContent}. This + * method will always be called <b>after</b> {@link #createControl}, so the + * implementation can assume that it can call {@link #getControl()} to obtain the + * corresponding control that the toolbar items will operate on. + * + * @param toolbar the toolbar to add the toolbar items to + */ + public void createToolbarItems(ToolBar toolbar); + + /** * Returns the control previously created by {@link #createControl(Composite)}. * @return A control to display in the {@link IDecorContent}. Must not be null. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiElementDetail.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiElementDetail.java index 593d657..2eca501 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiElementDetail.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiElementDetail.java @@ -268,7 +268,7 @@ class UiElementDetail implements IDetailsPage { FormText text = SectionHelper.createFormText(masterTable, toolkit, true /* isHtml */, tooltip, true /* setupLayoutData */); text.addHyperlinkListener(mTree.getEditor().createHyperlinkListener()); - Image icon = elem_desc.getIcon(); + Image icon = elem_desc.getCustomizedIcon(); if (icon != null) { text.setImage(DescriptorsUtils.IMAGE_KEY, icon); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java index 46b0a6b..451a6eb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java @@ -29,7 +29,7 @@ import org.eclipse.swt.graphics.Image; * UiModelTreeLabelProvider is a trivial implementation of {@link ILabelProvider} * where elements are expected to derive from {@link UiElementNode} or * from {@link ElementDescriptor}. - * + * * It is used by both the master tree viewer and by the list in the Add... selection dialog. */ public class UiModelTreeLabelProvider implements ILabelProvider { @@ -42,26 +42,27 @@ public class UiModelTreeLabelProvider implements ILabelProvider { */ public Image getImage(Object element) { ElementDescriptor desc = null; + UiElementNode node = null; + if (element instanceof ElementDescriptor) { - Image img = ((ElementDescriptor) element).getIcon(); - if (img != null) { - return img; - } + desc = (ElementDescriptor) element; } else if (element instanceof UiElementNode) { - UiElementNode node = (UiElementNode) element; + node = (UiElementNode) element; desc = node.getDescriptor(); - if (desc != null) { - Image img = desc.getIcon(); - if (img != null) { - if (node.hasError()) { - //TODO: cache image - return new ErrorImageComposite(img).createImage(); - } else { - return img; - } + } + + if (desc != null) { + Image img = desc.getCustomizedIcon(); + if (img != null) { + if (node != null && node.hasError()) { + //TODO: cache image + return new ErrorImageComposite(img).createImage(); + } else { + return img; } } } + return AdtPlugin.getAndroidLogo(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java index 596cfe3..9c822f6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.uimodel; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import org.eclipse.swt.widgets.Composite; @@ -38,8 +39,8 @@ public abstract class UiAttributeNode { private boolean mIsDirty; private boolean mHasError; - /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} - * and the corresponding runtine {@link UiElementNode} parent. */ + /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} + * and the corresponding runtime {@link UiElementNode} parent. */ public UiAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { mDescriptor = attributeDescriptor; mUiParent = uiParent; @@ -54,7 +55,7 @@ public abstract class UiAttributeNode { public final UiElementNode getUiParent() { return mUiParent; } - + /** Returns the current value of the node. */ public abstract String getCurrentValue(); @@ -78,10 +79,13 @@ public abstract class UiAttributeNode { mIsDirty = isDirty; // TODO: for unknown attributes, getParent() != null && getParent().getEditor() != null if (old_value != isDirty) { - getUiParent().getEditor().editorDirtyStateChanged(); + AndroidXmlEditor editor = getUiParent().getEditor(); + if (editor != null) { + editor.editorDirtyStateChanged(); + } } } - + /** * Sets the error flag value. * @param errorFlag the error flag @@ -89,21 +93,21 @@ public abstract class UiAttributeNode { public final void setHasError(boolean errorFlag) { mHasError = errorFlag; } - + /** * Returns whether this node has errors. */ public final boolean hasError() { return mHasError; } - + /** * Called once by the parent user interface to creates the necessary * user interface to edit this attribute. * <p/> * This method can be called more than once in the life cycle of an UI node, * typically when the UI is part of a master-detail tree, as pages are swapped. - * + * * @param parent The composite where to create the user interface. * @param managedForm The managed form owning this part. */ @@ -116,7 +120,7 @@ public abstract class UiAttributeNode { * for an attribute. * <p/> * Implementations that do not have any known values should return null. - * + * * @param prefix An optional prefix string, which is whatever the user has already started * typing. Can be null or an empty string. The implementation can use this to filter choices * and only return strings that match this prefix. A lazy or default implementation can @@ -136,7 +140,7 @@ public abstract class UiAttributeNode { * The caller doesn't really know if attributes have changed, * so it will call this to refresh the attribute anyway. It's up to the * UI implementation to minimize refreshes. - * + * * @param xml_attribute_node */ public abstract void updateValue(Node xml_attribute_node); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index 36cfc80..93b6baf 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -16,13 +16,14 @@ package com.android.ide.eclipse.adt.internal.editors.uimodel; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS; import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_URI; import static com.android.sdklib.SdkConstants.NS_RESOURCES; +import com.android.annotations.VisibleForTesting; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.resources.platform.AttributeInfo; import com.android.ide.eclipse.adt.AdtPlugin; @@ -210,17 +211,24 @@ public class UiElementNode implements IPropertySource { /** * Computes a short string describing the UI node suitable for tree views. * Uses the element's attribute "android:name" if present, or the "android:label" one - * followed by the element's name. + * followed by the element's name if not repeated. * * @return A short string describing the UI node suitable for tree views. */ public String getShortDescription() { + String name = mDescriptor.getUiName(); String attr = getDescAttribute(); if (attr != null) { - return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName()); + // If the ui name is repeated in the attribute value, don't use it. + // Typical case is to avoid ".pkg.MyActivity (Activity)". + if (attr.contains(name)) { + return attr; + } else { + return String.format("%1$s (%2$s)", attr, name); + } } - return mDescriptor.getUiName(); + return name; } /** Returns the key attribute that can be used to describe this node, or null */ @@ -1436,8 +1444,8 @@ public class UiElementNode implements IPropertySource { // --- for derived implementations only --- - // TODO doc - protected void setXmlNode(Node xmlNode) { + @VisibleForTesting + public void setXmlNode(Node xmlNode) { mXmlNode = xmlNode; } @@ -1538,7 +1546,7 @@ public class UiElementNode implements IPropertySource { * e.g. SdkConstants.NS_RESOURCES * @return The first prefix declared or the default "android" prefix. */ - private String lookupNamespacePrefix(Node node, String nsUri) { + public static String lookupNamespacePrefix(Node node, String nsUri) { // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java // The following code emulates this simple call: // String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES); @@ -1591,7 +1599,7 @@ public class UiElementNode implements IPropertySource { // We need to make sure the prefix is not one that was declared in the scope // visited above. Use a default namespace prefix "android" for the Android resource // NS and use "ns" for all other custom namespaces. - String prefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_PREFIX : "ns"; //$NON-NLS-1$ + String prefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "ns"; //$NON-NLS-1$ String base = prefix; for (int i = 1; visited.contains(prefix); i++) { prefix = base + Integer.toString(i); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java index cdb0c9a..c8e5720 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiListAttributeNode.java @@ -140,7 +140,7 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode { // FrameworkResourceManager expects a specific prefix for the attribute. String nsPrefix = ""; if (SdkConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) { - nsPrefix = LayoutConstants.ANDROID_NS_PREFIX + ':'; + nsPrefix = LayoutConstants.ANDROID_NS_NAME + ':'; } else if (XmlnsAttributeDescriptor.XMLNS_URI.equals(descriptor.getNamespaceUri())) { nsPrefix = XmlnsAttributeDescriptor.XMLNS_COLON; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java index 32e6167..2b7f346 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java @@ -16,13 +16,21 @@ package com.android.ide.eclipse.adt.internal.editors.uimodel; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_RESOURCE_REF; +import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; + +import com.android.ide.common.api.IAttributeInfo; +import com.android.ide.common.api.IAttributeInfo.Format; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog; @@ -46,6 +54,11 @@ import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.TableWrapData; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -59,7 +72,6 @@ import java.util.regex.Pattern; * See {@link UiTextAttributeNode} for more information. */ public class UiResourceAttributeNode extends UiTextAttributeNode { - private ResourceType mType; public UiResourceAttributeNode(ResourceType type, @@ -126,19 +138,19 @@ public class UiResourceAttributeNode extends UiTextAttributeNode { IProject project = editor.getProject(); if (project != null) { // get the resource repository for this project and the system resources. - IResourceRepository projectRepository = + ResourceRepository projectRepository = ResourceManager.getInstance().getProjectResources(project); if (mType != null) { // get the Target Data to get the system resources AndroidTargetData data = editor.getTargetData(); - IResourceRepository systemRepository = data.getSystemResources(); + ResourceRepository frameworkRepository = data.getFrameworkResources(); // open a resource chooser dialog for specified resource type. ResourceChooser dlg = new ResourceChooser(project, mType, projectRepository, - systemRepository, + frameworkRepository, shell); dlg.setCurrentResource(currentValue); @@ -194,13 +206,15 @@ public class UiResourceAttributeNode extends UiTextAttributeNode { */ @Override public String[] getPossibleValues(String prefix) { - IResourceRepository repository = null; - boolean isSystem = false; + return computeResourceStringMatches(getUiParent().getEditor(), getDescriptor(), prefix); + } - UiElementNode uiNode = getUiParent(); - AndroidXmlEditor editor = uiNode.getEditor(); + public static String[] computeResourceStringMatches(AndroidXmlEditor editor, + AttributeDescriptor attributeDescriptor, String prefix) { + ResourceRepository repository = null; + boolean isSystem = false; - if (prefix == null || prefix.indexOf("android:") < 0) { //$NON-NLS-1$ + if (prefix == null || !prefix.regionMatches(1, ANDROID_PKG, 0, ANDROID_PKG.length())) { IProject project = editor.getProject(); if (project != null) { // get the resource repository for this project and the system resources. @@ -208,18 +222,17 @@ public class UiResourceAttributeNode extends UiTextAttributeNode { } } else { // If there's a prefix with "android:" in it, use the system resources - // - // TODO find a way to only list *public* framework resources here. + // Non-public framework resources are filtered out later. AndroidTargetData data = editor.getTargetData(); - repository = data.getSystemResources(); + repository = data.getFrameworkResources(); isSystem = true; } // Get list of potential resource types, either specific to this project // or the generic list. - ResourceType[] resTypes = (repository != null) ? + Collection<ResourceType> resTypes = (repository != null) ? repository.getAvailableResourceTypes() : - ResourceType.values(); + EnumSet.allOf(ResourceType.class); // Get the type name from the prefix, if any. It's any word before the / if there's one String typeName = null; @@ -231,7 +244,7 @@ public class UiResourceAttributeNode extends UiTextAttributeNode { } // Now collect results - ArrayList<String> results = new ArrayList<String>(); + List<String> results = new ArrayList<String>(); if (typeName == null) { // This prefix does not have a / in it, so the resource string is either empty @@ -239,12 +252,23 @@ public class UiResourceAttributeNode extends UiTextAttributeNode { // resource types. for (ResourceType resType : resTypes) { - results.add("@" + resType.getName() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ + if (isSystem) { + results.add(PREFIX_ANDROID_RESOURCE_REF + resType.getName() + '/'); + } else { + results.add('@' + resType.getName() + '/'); + } if (resType == ResourceType.ID) { // Also offer the + version to create an id from scratch - results.add("@+" + resType.getName() + "/"); //$NON-NLS-1$ //$NON-NLS-2$ + results.add("@+" + resType.getName() + '/'); //$NON-NLS-1$ } } + + // Also add in @android: prefix to completion such that if user has typed + // "@an" we offer to complete it. + if (prefix == null || + ANDROID_PKG.regionMatches(0, prefix, 1, prefix.length() - 1)) { + results.add(PREFIX_ANDROID_RESOURCE_REF); + } } else if (repository != null) { // We have a style name and a repository. Find all resources that match this // type and recreate suggestions out of them. @@ -258,18 +282,112 @@ public class UiResourceAttributeNode extends UiTextAttributeNode { } if (isSystem) { - sb.append("android:"); //$NON-NLS-1$ + sb.append(ANDROID_PKG).append(':'); } sb.append(typeName).append('/'); String base = sb.toString(); - for (ResourceItem item : repository.getResources(resType)) { + for (ResourceItem item : repository.getResourceItemsOfType(resType)) { results.add(base + item.getName()); } } } + if (attributeDescriptor != null) { + sortAttributeChoices(attributeDescriptor, results); + } else { + Collections.sort(results); + } + return results.toArray(new String[results.size()]); } + + /** + * Attempts to sort the attribute values to bubble up the most likely choices to + * the top. + * <p> + * For example, if you are editing a style attribute, it's likely that among the + * resource values you would rather see @style or @android than @string. + */ + private static void sortAttributeChoices(AttributeDescriptor descriptor, + List<String> choices) { + final IAttributeInfo attributeInfo = descriptor.getAttributeInfo(); + Collections.sort(choices, new Comparator<String>() { + public int compare(String s1, String s2) { + int compare = score(attributeInfo, s1) - score(attributeInfo, s2); + if (compare == 0) { + // Sort alphabetically as a fallback + compare = s1.compareTo(s2); + } + return compare; + } + }); + } + + /** Compute a suitable sorting score for the given */ + private static final int score(IAttributeInfo attributeInfo, String value) { + if (value.equals(PREFIX_ANDROID_RESOURCE_REF)) { + return -1; + } + + for (Format format : attributeInfo.getFormats()) { + String type = null; + switch (format) { + case BOOLEAN: + type = "bool"; //$NON-NLS-1$ + break; + case COLOR: + type = "color"; //$NON-NLS-1$ + break; + case DIMENSION: + type = "dimen"; //$NON-NLS-1$ + break; + case INTEGER: + type = "integer"; //$NON-NLS-1$ + break; + case STRING: + type = "string"; //$NON-NLS-1$ + break; + // default: REFERENCE, FLAG, ENUM, etc - don't have type info about individual + // elements to help make a decision + } + + if (type != null) { + if (value.startsWith('@' + type + '/')) { + return -2; + } + + if (value.startsWith(PREFIX_ANDROID_RESOURCE_REF + type + '/')) { + return -2; + } + } + } + + // Handle a few more cases not covered by the Format metadata check + String type = null; + + String attribute = attributeInfo.getName(); + if (attribute.equals(ATTR_ID)) { + type = "id"; //$NON-NLS-1$ + } else if (attribute.equals(ATTR_STYLE)) { + type = "style"; //$NON-NLS-1$ + } else if (attribute.equals(LayoutDescriptors.ATTR_LAYOUT)) { + type = "layout"; //$NON-NLS-1$ + } else if (attribute.equals("drawable")) { //$NON-NLS-1$ + type = "drawable"; //$NON-NLS-1$ + } + + if (type != null) { + if (value.startsWith('@' + type + '/')) { + return -2; + } + + if (value.startsWith(PREFIX_ANDROID_RESOURCE_REF + type + '/')) { + return -2; + } + } + + return 0; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java index 29545d5..83e9bc4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/Hyperlinks.java @@ -21,40 +21,47 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS; import static com.android.ide.common.layout.LayoutConstants.ATTR_ON_CLICK; import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; import static com.android.ide.common.layout.LayoutConstants.VIEW; -import static com.android.ide.eclipse.adt.AndroidConstants.ANDROID_PKG; -import static com.android.ide.eclipse.adt.AndroidConstants.EXT_XML; -import static com.android.ide.eclipse.adt.AndroidConstants.FN_RESOURCE_BASE; -import static com.android.ide.eclipse.adt.AndroidConstants.FN_RESOURCE_CLASS; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_RESOURCE_REF; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF; +import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.FN_RESOURCE_BASE; +import static com.android.ide.eclipse.adt.AdtConstants.FN_RESOURCE_CLASS; import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.ROOT_ELEMENT; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.STYLE_ELEMENT; +import static com.android.sdklib.SdkConstants.FD_DOCS; +import static com.android.sdklib.SdkConstants.FD_DOCS_REFERENCE; import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_NAME; import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_PACKAGE; import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY; import static com.android.sdklib.xml.AndroidManifest.NODE_SERVICE; import com.android.annotations.VisibleForTesting; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor; import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.manager.FolderTypeRelationship; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.io.IFileWrapper; import com.android.ide.eclipse.adt.io.IFolderWrapper; +import com.android.io.FileWrapper; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.FileWrapper; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; import com.android.util.Pair; import org.apache.xerces.parsers.DOMParser; @@ -83,7 +90,6 @@ import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; @@ -108,6 +114,7 @@ import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchPage; @@ -139,6 +146,8 @@ import org.xml.sax.SAXException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -153,6 +162,14 @@ import java.util.regex.Pattern; */ @SuppressWarnings("restriction") public class Hyperlinks { + private static final String CATEGORY = "category"; //$NON-NLS-1$ + private static final String ACTION = "action"; //$NON-NLS-1$ + private static final String PERMISSION = "permission"; //$NON-NLS-1$ + private static final String USES_PERMISSION = "uses-permission"; //$NON-NLS-1$ + private static final String CATEGORY_PKG_PREFIX = "android.intent.category."; //$NON-NLS-1$ + private static final String ACTION_PKG_PREFIX = "android.intent.action."; //$NON-NLS-1$ + private static final String PERMISSION_PKG_PREFIX = "android.permission."; //$NON-NLS-1$ + private Hyperlinks() { // Not instantiatable. This is a container class containing shared code // for the various inner classes that are actual hyperlink resolvers. @@ -164,7 +181,7 @@ public class Hyperlinks { "(([a-zA-Z_\\$][a-zA-Z0-9_\\$]*)+\\.)+[a-zA-Z_\\$][a-zA-Z0-9_\\$]*"); //$NON-NLS-1$ /** Determines whether the given attribute <b>name</b> is linkable */ - private static boolean isAttributeNameLink(@SuppressWarnings("unused") XmlContext context) { + private static boolean isAttributeNameLink(XmlContext context) { // We could potentially allow you to link to builtin Android properties: // ANDROID_URI.equals(attribute.getNamespaceURI()) // and then jump into the res/values/attrs.xml document that is available @@ -187,8 +204,8 @@ public class Hyperlinks { return false; } - if (isClassAttribute(context) || isOnClickAttribute(context) || isActivity(context) - || isService(context)) { + if (isClassAttribute(context) || isOnClickAttribute(context) + || isManifestName(context) || isStyleAttribute(context)) { return true; } @@ -200,7 +217,7 @@ public class Hyperlinks { return false; } - Pair<ResourceType,String> resource = parseResource(value); + Pair<ResourceType,String> resource = ResourceHelper.parseResource(value); if (resource != null) { ResourceType type = resource.getFirst(); if (type != null) { @@ -237,6 +254,79 @@ public class Hyperlinks { return false; } + /** + * Returns true if this node/attribute pair corresponds to a manifest android:name reference + */ + private static boolean isManifestName(XmlContext context) { + Attr attribute = context.getAttribute(); + if (attribute != null && ATTRIBUTE_NAME.equals(attribute.getLocalName()) + && ANDROID_URI.equals(attribute.getNamespaceURI())) { + if (getEditor() instanceof ManifestEditor) { + return true; + } + } + + return false; + } + + /** + * Opens the declaration corresponding to an android:name reference in the + * AndroidManifest.xml file + */ + private static boolean openManifestName(IProject project, XmlContext context) { + if (isActivity(context)) { + String fqcn = getActivityClassFqcn(context); + return AdtPlugin.openJavaClass(project, fqcn); + } else if (isService(context)) { + String fqcn = getServiceClassFqcn(context); + return AdtPlugin.openJavaClass(project, fqcn); + } else if (isBuiltinPermission(context)) { + String permission = context.getAttribute().getValue(); + // Mutate something like android.permission.ACCESS_CHECKIN_PROPERTIES + // into relative doc url android/Manifest.permission.html#ACCESS_CHECKIN_PROPERTIES + assert permission.startsWith(PERMISSION_PKG_PREFIX); + String relative = "android/Manifest.permission.html#" //$NON-NLS-1$ + + permission.substring(PERMISSION_PKG_PREFIX.length()); + + URL url = getDocUrl(relative); + if (url != null) { + AdtPlugin.openUrl(url); + return true; + } else { + return false; + } + } else if (isBuiltinIntent(context)) { + String intent = context.getAttribute().getValue(); + // Mutate something like android.intent.action.MAIN into + // into relative doc url android/content/Intent.html#ACTION_MAIN + String relative; + if (intent.startsWith(ACTION_PKG_PREFIX)) { + relative = "android/content/Intent.html#ACTION_" //$NON-NLS-1$ + + intent.substring(ACTION_PKG_PREFIX.length()); + } else if (intent.startsWith(CATEGORY_PKG_PREFIX)) { + relative = "android/content/Intent.html#CATEGORY_" //$NON-NLS-1$ + + intent.substring(CATEGORY_PKG_PREFIX.length()); + } else { + return false; + } + URL url = getDocUrl(relative); + if (url != null) { + AdtPlugin.openUrl(url); + return true; + } else { + return false; + } + } + + return false; + } + + /** Returns true if this represents a {@code <view class="foo.bar.Baz">} class attribute */ + private static boolean isStyleAttribute(XmlContext context) { + String tag = context.getElement().getTagName(); + return STYLE_ELEMENT.equals(tag); + } + /** Returns true if this represents a {@code <view class="foo.bar.Baz">} class attribute */ private static boolean isClassAttribute(XmlContext context) { Attr attribute = context.getAttribute(); @@ -298,6 +388,72 @@ public class Hyperlinks { } /** + * Returns a URL pointing to the Android reference documentation, either installed + * locally or the one on android.com + * + * @param relative a relative url to append to the root url + * @return a URL pointing to the documentation + */ + private static URL getDocUrl(String relative) { + // First try to find locally installed documentation + File sdkLocation = new File(Sdk.getCurrent().getSdkLocation()); + File docs = new File(sdkLocation, FD_DOCS + File.separator + FD_DOCS_REFERENCE); + try { + if (docs.exists()) { + String s = docs.toURI().toURL().toExternalForm(); + if (!s.endsWith("/")) { //$NON-NLS-1$ + s += "/"; //$NON-NLS-1$ + } + return new URL(s + relative); + } + // If not, fallback to the online documentation + return new URL("http://developer.android.com/reference/" + relative); //$NON-NLS-1$ + } catch (MalformedURLException e) { + AdtPlugin.log(e, "Can't create URL for %1$s", docs); + return null; + } + } + + /** Returns true if the context is pointing to a permission name reference */ + private static boolean isBuiltinPermission(XmlContext context) { + Attr attribute = context.getAttribute(); + Element node = context.getElement(); + + // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it + String nodeName = node.getNodeName(); + if ((USES_PERMISSION.equals(nodeName) || PERMISSION.equals(nodeName)) + && ATTRIBUTE_NAME.equals(attribute.getLocalName()) + && ANDROID_URI.equals(attribute.getNamespaceURI())) { + String value = attribute.getValue(); + if (value.startsWith(PERMISSION_PKG_PREFIX)) { + return true; + } + } + + return false; + } + + /** Returns true if the context is pointing to an intent reference */ + private static boolean isBuiltinIntent(XmlContext context) { + Attr attribute = context.getAttribute(); + Element node = context.getElement(); + + // Is this an <activity> or <service> in an AndroidManifest.xml file? If so, jump to it + String nodeName = node.getNodeName(); + if ((ACTION.equals(nodeName) || CATEGORY.equals(nodeName)) + && ATTRIBUTE_NAME.equals(attribute.getLocalName()) + && ANDROID_URI.equals(attribute.getNamespaceURI())) { + String value = attribute.getValue(); + if (value.startsWith(ACTION_PKG_PREFIX) || value.startsWith(CATEGORY_PKG_PREFIX)) { + return true; + } + } + + return false; + } + + + /** * Returns the fully qualified class name of an activity referenced by the given * AndroidManifest.xml node */ @@ -307,8 +463,17 @@ public class Hyperlinks { StringBuilder sb = new StringBuilder(); Element root = node.getOwnerDocument().getDocumentElement(); String pkg = root.getAttribute(ATTRIBUTE_PACKAGE); - sb.append(pkg); String className = attribute.getValue(); + if (className.startsWith(".")) { //$NON-NLS-1$ + sb.append(pkg); + } else if (className.indexOf('.') == -1) { + // According to the <activity> manifest element documentation, this is not + // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html ) + // but it appears in manifest files and appears to be supported by the runtime + // so handle this in code as well: + sb.append(pkg); + sb.append('.'); + } // else: the class name is already a fully qualified class name sb.append(className); return sb.toString(); } @@ -323,24 +488,6 @@ public class Hyperlinks { } /** - * Is this a resource that can be defined in any file within the "values" folder? - * - * @param type the resource type to check - * @return true if the given resource type can be represented as a value under the - * values/ folder - */ - public static boolean isValueResource(ResourceType type) { - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); - for (ResourceFolderType folderType : folderTypes) { - if (folderType == ResourceFolderType.VALUES) { - return true; - } - } - - return false; - } - - /** * Returns the XML tag containing an element description for value items of the given * resource type * @@ -369,14 +516,10 @@ public class Hyperlinks { return false; } - if (isActivity(context)) { - String fqcn = getActivityClassFqcn(context); - return openJavaClass(project, fqcn); - } else if (isService(context)) { - String fqcn = getServiceClassFqcn(context); - return openJavaClass(project, fqcn); + if (isManifestName(context)) { + return openManifestName(project, context); } else if (isClassElement(context) || isClassAttribute(context)) { - return openJavaClass(project, getClassFqcn(context)); + return AdtPlugin.openJavaClass(project, getClassFqcn(context)); } else if (isOnClickAttribute(context)) { return openOnClickMethod(project, context.getAttribute().getValue()); } else { @@ -384,21 +527,6 @@ public class Hyperlinks { } } - /** Opens the given file and shows the given (optional) region */ - private static void openFile(IFile file, IRegion region) throws PartInitException { - IEditorPart sourceEditor = getEditor(); - IWorkbenchPage page = sourceEditor.getEditorSite().getPage(); - IEditorPart targetEditor = IDE.openEditor(page, file, true); - if (targetEditor instanceof AndroidXmlEditor) { - AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor; - if ((region != null)) { - editor.show(region.getOffset(), region.getLength()); - } else { - editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); - } - } - } - /** Opens a path (which may not be in the workspace) */ private static void openPath(IPath filePath, IRegion region, int offset) { IEditorPart sourceEditor = getEditor(); @@ -410,7 +538,7 @@ public class Hyperlinks { IResource file = workspace.findMember(relativePath); if (file instanceof IFile) { try { - openFile((IFile)file, region); + AdtPlugin.openFile((IFile) file, region); return; } catch (PartInitException ex) { AdtPlugin.log(ex, "Can't open %$1s", filePath); //$NON-NLS-1$ @@ -459,38 +587,6 @@ public class Hyperlinks { } /** - * Opens a Java class for the given fully qualified class name - * - * @param project the project containing the class - * @param fqcn the fully qualified class name of the class to be opened - * @return true if the class was opened, false otherwise - */ - public static boolean openJavaClass(IProject project, String fqcn) { - if (fqcn == null) { - return false; - } - - // Handle inner classes - if (fqcn.indexOf('$') != -1) { - fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$ - } - - try { - if (project.hasNature(JavaCore.NATURE_ID)) { - IJavaProject javaProject = JavaCore.create(project); - IJavaElement result = javaProject.findType(fqcn); - if (result != null) { - return JavaUI.openInEditor(result) != null; - } - } - } catch (Throwable e) { - AdtPlugin.log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$ - } - - return false; - } - - /** * Opens a Java method referenced by the given on click attribute method name * * @param project the project containing the click handler @@ -525,7 +621,7 @@ public class Hyperlinks { try { IJavaSearchScope scope = null; IType activityType = null; - IJavaProject javaProject = (IJavaProject) project.getNature(JavaCore.NATURE_ID); + IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); if (javaProject != null) { activityType = javaProject.findType(SdkConstants.CLASS_ACTIVITY); if (activityType != null) { @@ -590,10 +686,11 @@ public class Hyperlinks { } // Create a configuration from the current file + IProject project = null; IEditorInput editorInput = editor.getEditorInput(); if (editorInput instanceof FileEditorInput) { IFile file = ((FileEditorInput) editorInput).getFile(); - IProject project = file.getProject(); + project = file.getProject(); ProjectResources pr = ResourceManager.getInstance().getProjectResources(project); IContainer parent = file.getParent(); if (parent instanceof IFolder) { @@ -603,6 +700,22 @@ public class Hyperlinks { } } } + + // Might be editing a Java file, where there is no configuration context. + // Instead look at surrounding files in the workspace and obtain one valid + // configuration. + for (IEditorReference reference : editor.getSite().getPage().getEditorReferences()) { + IEditorPart part = reference.getEditor(false /*restore*/); + if (part instanceof LayoutEditor) { + LayoutEditor layoutEditor = (LayoutEditor) part; + if (project == null || layoutEditor.getProject() == project) { + GraphicalEditorPart graphicalEditor = layoutEditor.getGraphicalEditor(); + if (graphicalEditor != null) { + return graphicalEditor.getConfiguration(); + } + } + } + } } return null; @@ -630,7 +743,7 @@ public class Hyperlinks { } /** Return either the project resources or the framework resources (or null) */ - private static ProjectResources getResources(IProject project, boolean framework) { + private static ResourceRepository getResources(IProject project, boolean framework) { if (framework) { IAndroidTarget target = getTarget(project); if (target == null) { @@ -662,7 +775,7 @@ public class Hyperlinks { } // Look in the configuration folder: Search compatible configurations - ProjectResources resources = getResources(project, false /* isFramework */); + ResourceRepository resources = getResources(project, false /* isFramework */); FolderConfiguration configuration = getConfiguration(); if (configuration != null) { // Not the case when searching from Java files for example List<ResourceFolder> folders = resources.getFolders(ResourceFolderType.LAYOUT); @@ -867,34 +980,6 @@ public class Hyperlinks { return null; } - /** Return the resource type of the given url, and the resource name */ - private static Pair<ResourceType,String> parseResource(String url) { - if (!url.startsWith("@")) { //$NON-NLS-1$ - return null; - } - int typeEnd = url.indexOf('/', 1); - if (typeEnd == -1) { - return null; - } - int nameBegin = typeEnd + 1; - - // Skip @ and @+ - int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$ - - int colon = url.lastIndexOf(':', typeEnd); - if (colon != -1) { - typeBegin = colon + 1; - } - String typeName = url.substring(typeBegin, typeEnd); - ResourceType type = ResourceType.getEnum(typeName); - if (type == null) { - return null; - } - String name = url.substring(nameBegin); - - return Pair.of(type, name); - } - /** Parses the given file and locates a definition of the given resource */ private static Pair<File, Integer> findValueInXml(ResourceType type, String name, File file) { // We can't use the StructureModelManager on files outside projects @@ -950,16 +1035,46 @@ public class Hyperlinks { return null; } + private static IHyperlink[] getStyleLinks(XmlContext context, IRegion range, String url) { + Attr attribute = context.getAttribute(); + if (attribute != null) { + // Split up theme resource urls to the nearest dot forwards, such that you + // can point to "Theme.Light" by placing the caret anywhere after the dot, + // and point to just "Theme" by pointing before it. + int caret = context.getInnerRegionCaretOffset(); + String value = attribute.getValue(); + int index = value.indexOf('.', caret); + if (index != -1) { + url = url.substring(0, index); + range = new Region(range.getOffset(), + range.getLength() - (value.length() - index)); + } + } + + Pair<ResourceType,String> resource = ResourceHelper.parseResource(url); + if (resource == null) { + String androidStyle = "@android:style/"; //$NON-NLS-1$ + if (url.startsWith(PREFIX_ANDROID_RESOURCE_REF)) { + url = androidStyle + url.substring(PREFIX_ANDROID_RESOURCE_REF.length()); + } else if (url.startsWith(ANDROID_PKG + ':')) { + url = androidStyle + url.substring(ANDROID_PKG.length() + 1); + } else { + url = "@style/" + url; //$NON-NLS-1$ + } + } + return getResourceLinks(range, url); + } + /** - * Computes hyperlinks to resource definitions for resource urls (e.g. {@code - * @android:string/ok} or {@code @layout/foo}. May create multiple links. + * Computes hyperlinks to resource definitions for resource urls (e.g. + * {@code @android:string/ok} or {@code @layout/foo}. May create multiple links. */ private static IHyperlink[] getResourceLinks(IRegion range, String url) { List<IHyperlink> links = new ArrayList<IHyperlink>(); IProject project = Hyperlinks.getProject(); FolderConfiguration configuration = getConfiguration(); - Pair<ResourceType,String> resource = parseResource(url); + Pair<ResourceType,String> resource = ResourceHelper.parseResource(url); if (resource == null || resource.getFirst() == null) { return null; } @@ -968,7 +1083,7 @@ public class Hyperlinks { boolean isFramework = url.startsWith("@android"); //$NON-NLS-1$ - ProjectResources resources = getResources(project, isFramework); + ResourceRepository resources = getResources(project, isFramework); if (resources == null) { return null; } @@ -1003,8 +1118,7 @@ public class Hyperlinks { }); // Is this something found in a values/ folder? - boolean valueResource = isValueResource(type); - //boolean fileResource = isFileResource(type); + boolean valueResource = ResourceHelper.isValueBasedResourceType(type); for (ResourceFile file : matches) { String folderName = file.getFolder().getFolder().getName(); @@ -1066,7 +1180,11 @@ public class Hyperlinks { range = new Region(range.getOffset() + 1, range.getLength() - 2); Attr attribute = context.getAttribute(); - if (attribute != null && attribute.getValue().startsWith("@")) { //$NON-NLS-1$ + if (isStyleAttribute(context)) { + return getStyleLinks(context, range, attribute.getValue()); + } + if (attribute != null + && attribute.getValue().startsWith(PREFIX_RESOURCE_REF)) { // Instantly create links for resources since we can use the existing // resolved maps for this and offer multiple choices for the user @@ -1082,6 +1200,53 @@ public class Hyperlinks { if (isElementNameLink(context)) { isLinkable = true; } + } else if (type == DOMRegionContext.XML_CONTENT) { + Node parentNode = context.getNode().getParentNode(); + if (parentNode != null && parentNode.getNodeType() == Node.ELEMENT_NODE) { + // Try to complete resources defined inline as text, such as + // style definitions + ITextRegion outer = context.getElementRegion(); + ITextRegion inner = context.getInnerRegion(); + int innerOffset = outer.getStart() + inner.getStart(); + int caretOffset = innerOffset + context.getInnerRegionCaretOffset(); + try { + IRegion lineInfo = document.getLineInformationOfOffset(caretOffset); + int lineStart = lineInfo.getOffset(); + int lineEnd = Math.min(lineStart + lineInfo.getLength(), + innerOffset + inner.getLength()); + + // Compute the resource URL + int urlStart = -1; + int offset = caretOffset; + while (offset > lineStart) { + char c = document.getChar(offset); + if (c == '@') { + urlStart = offset; + break; + } else if (!isValidResourceUrlChar(c)) { + break; + } + offset--; + } + + if (urlStart != -1) { + offset = caretOffset; + while (offset < lineEnd) { + if (!isValidResourceUrlChar(document.getChar(offset))) { + break; + } + offset++; + } + + int length = offset - urlStart; + String url = document.get(urlStart, length); + range = new Region(urlStart, length); + return getResourceLinks(range, url); + } + } catch (BadLocationException e) { + AdtPlugin.log(e, null); + } + } } if (isLinkable) { @@ -1097,6 +1262,11 @@ public class Hyperlinks { } } + private static boolean isValidResourceUrlChar(char c) { + return Character.isJavaIdentifierPart(c) || c == ':' || c == '/' || c == '.' || c == '+'; + + } + /** Detector for finding Android references in Java files */ public static class JavaResolver extends AbstractHyperlinkDetector { @@ -1180,7 +1350,7 @@ public class Hyperlinks { } /** Returns the editor applicable to this hyperlink detection */ - private static IEditorPart getEditor() { + public static IEditorPart getEditor() { // I would like to be able to find this via getAdapter(TextEditor.class) but // couldn't find a way to initialize the editor context from // AndroidSourceViewerConfig#getHyperlinkDetectorTargets (which only has @@ -1264,7 +1434,7 @@ public class Hyperlinks { * is known, but the value location within XML files is deferred until the link is * actually opened. */ - private static class ResourceLink implements IHyperlink { + static class ResourceLink implements IHyperlink { private final String mLinkText; private final IRegion mLinkRegion; private final ResourceType mType; @@ -1315,7 +1485,7 @@ public class Hyperlinks { Pair<IFile,IRegion> def = findIdDefinition(project, mName); if (def != null) { try { - openFile(def.getFirst(), def.getSecond()); + AdtPlugin.openFile(def.getFirst(), def.getSecond()); } catch (PartInitException e) { AdtPlugin.log(e, null); } @@ -1332,13 +1502,14 @@ public class Hyperlinks { try { // Lazily search for the target? IRegion region = null; - if (mType != null && mName != null && EXT_XML.equals(file.getFileExtension())) { + String extension = file.getFileExtension(); + if (mType != null && mName != null && EXT_XML.equals(extension)) { Pair<IFile, IRegion> target = findValueInXml(mType, mName, file); if (target != null) { region = target.getSecond(); } } - openFile(file, region); + AdtPlugin.openFile(file, region); } catch (PartInitException e) { AdtPlugin.log(e, null); } @@ -1360,6 +1531,10 @@ public class Hyperlinks { throw new IllegalArgumentException("Invalid link parameters"); } } + + ResourceFile getFile() { + return mFile; + } } /** @@ -1367,18 +1542,23 @@ public class Hyperlinks { * particular caret offset */ private static class XmlContext { + private final Node mNode; private final Element mElement; private final Attr mAttribute; private final IStructuredDocumentRegion mOuterRegion; private final ITextRegion mInnerRegion; + private final int mInnerRegionOffset; - public XmlContext(Element element, Attr attribute, IStructuredDocumentRegion outerRegion, - ITextRegion innerRegion) { + public XmlContext(Node node, Element element, Attr attribute, + IStructuredDocumentRegion outerRegion, + ITextRegion innerRegion, int innerRegionOffset) { super(); + mNode = node; mElement = element; mAttribute = attribute; mOuterRegion = outerRegion; mInnerRegion = innerRegion; + mInnerRegionOffset = innerRegionOffset; } /** @@ -1386,6 +1566,16 @@ public class Hyperlinks { * * @return the surrounding node */ + public Node getNode() { + return mNode; + } + + + /** + * Gets the current node, may be null + * + * @return the surrounding node + */ public Element getElement() { return mElement; } @@ -1419,6 +1609,15 @@ public class Hyperlinks { } /** + * Gets the caret offset relative to the inner region + * + * @return the offset relative to the inner region + */ + public int getInnerRegionCaretOffset() { + return mInnerRegionOffset; + } + + /** * Returns a range with suffix whitespace stripped out * * @param document the document containing the regions @@ -1480,8 +1679,25 @@ public class Hyperlinks { if (region != null && DOMRegionContext.XML_TAG_NAME.equals(region.getType())) { ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); - return new XmlContext(element, attribute, region, subRegion); + int regionStart = region.getStartOffset(); + int subregionStart = subRegion.getStart(); + int relativeOffset = offset - (regionStart + subregionStart); + return new XmlContext(element, element, attribute, region, subRegion, + relativeOffset); + } + } else if (inode instanceof Node) { + IStructuredDocument doc = model.getStructuredDocument(); + IStructuredDocumentRegion region = doc.getRegionAtCharacterOffset(offset); + if (region != null + && DOMRegionContext.XML_CONTENT.equals(region.getType())) { + ITextRegion subRegion = region.getRegionAtCharacterOffset(offset); + int regionStart = region.getStartOffset(); + int subregionStart = subRegion.getStart(); + int relativeOffset = offset - (regionStart + subregionStart); + return new XmlContext((Node) inode, null, null, region, subRegion, + relativeOffset); } + } } } finally { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/XmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/XmlEditor.java index 177cb14..4cd2ed5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/XmlEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/XmlEditor.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.xml; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.FirstElementParser; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; @@ -30,6 +30,7 @@ import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IStatus; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.PartInitException; @@ -41,7 +42,7 @@ import org.w3c.dom.Document; */ public class XmlEditor extends AndroidXmlEditor { - public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".xml.XmlEditor"; //$NON-NLS-1$ + public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".xml.XmlEditor"; //$NON-NLS-1$ /** Root node of the UI element hierarchy */ private UiDocumentNode mUiRootNode; @@ -73,9 +74,15 @@ public class XmlEditor extends AndroidXmlEditor { * @return True if the {@link XmlEditor} can handle that file. */ public static boolean canHandleFile(IFile file) { + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, "canHandleFile(%1$s)", file.getFullPath().toOSString()); + } // we need the target of the file's project to access the descriptors. IProject project = file.getProject(); IAndroidTarget target = Sdk.getCurrent().getTarget(project); + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " target=%1$s", target); + } if (target != null) { // Note: the target data can be null when an SDK is not finished loading yet. // We can potentially arrive here when Eclipse is started with a file previously @@ -85,9 +92,16 @@ public class XmlEditor extends AndroidXmlEditor { FirstElementParser.Result result = FirstElementParser.parse( file.getLocation().toOSString(), SdkConstants.NS_RESOURCES); + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " data=%1$s, result=%2$s", data, result); + } if (result != null && data != null) { String name = result.getElement(); + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " name=%1$s, xmlnsprefix=%2$s", name, + result.getXmlnsPrefix()); + } if (name != null && result.getXmlnsPrefix() != null) { DocumentDescriptor desc = data.getXmlDescriptors().getDescriptor(); for (ElementDescriptor elem : desc.getChildren()) { @@ -100,6 +114,10 @@ public class XmlEditor extends AndroidXmlEditor { } } + if (AdtPlugin.DEBUG_XML_FILE_INIT) { + AdtPlugin.log(IStatus.INFO, " File cannot be handled"); + } + return false; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/descriptors/XmlDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/descriptors/XmlDescriptors.java index b807aa1..bc74d57 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/descriptors/XmlDescriptors.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/xml/descriptors/XmlDescriptors.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.xml.descriptors; -import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_NS_NAME; import com.android.ide.common.resources.platform.AttributeInfo; import com.android.ide.common.resources.platform.DeclareStyleableInfo; @@ -134,7 +134,7 @@ public final class XmlDescriptors implements IDescriptorProvider { ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) { XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( - ANDROID_NS_PREFIX, + ANDROID_NS_NAME, SdkConstants.NS_RESOURCES); ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java index 6ee74ed..4db603e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java @@ -44,8 +44,8 @@ import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.NullSdkLog; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdklib.xml.ManifestData; import org.eclipse.core.resources.IFile; @@ -1187,7 +1187,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // build the command line based on the available parameters. ArrayList<String> list = new ArrayList<String>(); - list.add(AdtPlugin.getOsAbsoluteEmulator()); + String path = avdToLaunch.getEmulatorPath(AdtPlugin.getOsSdkFolder()); + + list.add(path); + list.add(FLAG_AVD); list.add(avdToLaunch.getName()); @@ -1661,4 +1664,3 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } } - diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java index a9c8af8..b0dfea3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/DeviceChooserDialog.java @@ -29,7 +29,7 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdkuilib.internal.widgets.AvdSelector; import com.android.sdkuilib.internal.widgets.AvdSelector.DisplayMode; import com.android.sdkuilib.internal.widgets.AvdSelector.IAvdFilter; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java index 3705cc2..1fc72fb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/EmulatorConfigTab.java @@ -26,8 +26,8 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.NullSdkLog; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdkuilib.internal.widgets.AvdSelector; import com.android.sdkuilib.internal.widgets.AvdSelector.DisplayMode; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/JUnitLaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/JUnitLaunchConfigDelegate.java index cbfcb24..442d356 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/JUnitLaunchConfigDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/JUnitLaunchConfigDelegate.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.launch; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.sdklib.SdkConstants; import org.eclipse.core.runtime.CoreException; @@ -149,7 +149,7 @@ public class JUnitLaunchConfigDelegate extends JUnitLaunchConfigurationDelegate if (bundle == null) { throw new IOException("Cannot find org.junit bundle"); } - URL jarUrl = bundle.getEntry(AndroidConstants.WS_SEP + JUNIT_JAR); + URL jarUrl = bundle.getEntry(AdtConstants.WS_SEP + JUNIT_JAR); return FileLocator.resolve(jarUrl).getFile(); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java index e747e7e..6364dcd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/LaunchConfigDelegate.java @@ -18,7 +18,7 @@ package com.android.ide.eclipse.adt.internal.launch; import com.android.ddmlib.AndroidDebugBridge; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration.TargetMode; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; @@ -380,7 +380,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { return false; } - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { String msg = String.format("%1$s is not an Android project!", project.getName()); AdtPlugin.displayError("Android Launch", msg); return false; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigDelegate.java index 1f14605..2384f3b 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigDelegate.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.launch.junit; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.launch.AndroidLaunch; import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchConfiguration; import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchController; @@ -188,7 +188,7 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate { LaunchMessages.AndroidJUnitDelegate_NoRunnerConsoleMsg_4s, project.getName(), SdkConstants.CLASS_INSTRUMENTATION_RUNNER, - AndroidConstants.LIBRARY_TEST_RUNNER, + AdtConstants.LIBRARY_TEST_RUNNER, SdkConstants.FN_ANDROID_MANIFEST_XML)); return null; } catch (CoreException e) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java index e5957d7..51db066 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.launch.junit; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.launch.LaunchMessages; import com.android.ide.eclipse.adt.internal.launch.MainLaunchConfigTab; @@ -659,7 +659,7 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat validateJavaProject(javaProject); try { - if (!project.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (!project.hasNature(AdtConstants.NATURE_DEFAULT)) { setErrorMessage( LaunchMessages.NonAndroidProjectError); return; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/InstrumentationRunnerValidator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/InstrumentationRunnerValidator.java index bb75bcc..3234b38 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/InstrumentationRunnerValidator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/InstrumentationRunnerValidator.java @@ -15,7 +15,7 @@ */ package com.android.ide.eclipse.adt.internal.launch.junit; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.launch.LaunchMessages; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; @@ -88,7 +88,7 @@ class InstrumentationRunnerValidator { */ private boolean hasTestRunnerLibrary(ManifestData manifestData) { for (UsesLibrary lib : manifestData.getUsesLibraries()) { - if (AndroidConstants.LIBRARY_TEST_RUNNER.equals(lib.getName())) { + if (AdtConstants.LIBRARY_TEST_RUNNER.equals(lib.getName())) { return true; } } @@ -130,7 +130,7 @@ class InstrumentationRunnerValidator { String validateInstrumentationRunner(String instrumentation) { if (!mHasRunnerLibrary) { return String.format(LaunchMessages.InstrValidator_NoTestLibMsg_s, - AndroidConstants.LIBRARY_TEST_RUNNER); + AdtConstants.LIBRARY_TEST_RUNNER); } // check if this instrumentation is the standard test runner if (!instrumentation.equals(SdkConstants.CLASS_INSTRUMENTATION_RUNNER)) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java index 0f6b508..9ae60c0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java @@ -18,7 +18,7 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.AndroidVersion; @@ -67,6 +67,8 @@ import java.util.regex.Pattern; */ public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer { + public static final String NULL_API_URL = "<null>"; //$NON-NLS-1$ + public static final String SOURCES_ZIP = "/sources.zip"; //$NON-NLS-1$ public static final String COM_ANDROID_IDE_ECLIPSE_ADT_SOURCE = @@ -293,7 +295,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit } try { - BaseProjectHelper.markProject(iProject, AndroidConstants.MARKER_TARGET, + BaseProjectHelper.markProject(iProject, AdtConstants.MARKER_TARGET, markerMessage, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); } catch (CoreException e) { // In some cases, the workspace may be locked for modification when we @@ -305,7 +307,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit protected IStatus run(IProgressMonitor monitor) { try { BaseProjectHelper.markProject(iProject, - AndroidConstants.MARKER_TARGET, + AdtConstants.MARKER_TARGET, fmessage, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH); } catch (CoreException e2) { @@ -324,7 +326,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit // no error, remove potential MARKER_TARGETs. try { if (iProject.exists()) { - iProject.deleteMarkers(AndroidConstants.MARKER_TARGET, true, + iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true, IResource.DEPTH_INFINITE); } } catch (CoreException ce) { @@ -334,7 +336,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit @Override protected IStatus run(IProgressMonitor monitor) { try { - iProject.deleteMarkers(AndroidConstants.MARKER_TARGET, true, + iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true, IResource.DEPTH_INFINITE); } catch (CoreException e2) { return e2.getStatus(); @@ -530,8 +532,7 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit } } IClasspathAttribute[] attributes = null; - if (apiURL != null) { - + if (apiURL != null && !NULL_API_URL.equals(apiURL)) { IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute( IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, apiURL); attributes = new IClasspathAttribute[] { @@ -824,12 +825,22 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit getAndroidSourceProperty(target), null); } IClasspathAttribute[] extraAttributtes = entry.getExtraAttributes(); + if (extraAttributtes.length == 0) { + ProjectHelper.saveStringProperty(root, PROPERTY_ANDROID_API, + NULL_API_URL); + } for (int j = 0; j < extraAttributtes.length; j++) { IClasspathAttribute extraAttribute = extraAttributtes[j]; + String value = extraAttribute.getValue(); + if ((value == null || value.trim().length() == 0) + && IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME + .equals(extraAttribute.getName())) { + value = NULL_API_URL; + } if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME .equals(extraAttribute.getName())) { ProjectHelper.saveStringProperty(root, - PROPERTY_ANDROID_API, extraAttribute.getValue()); + PROPERTY_ANDROID_API, value); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java index cafd268..ba47c7a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidManifestHelper.java @@ -19,9 +19,9 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener; import com.android.ide.eclipse.adt.io.IFileWrapper; -import com.android.sdklib.io.FileWrapper; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.StreamException; +import com.android.io.FileWrapper; +import com.android.io.IAbstractFile; +import com.android.io.StreamException; import com.android.sdklib.xml.AndroidManifestParser; import com.android.sdklib.xml.ManifestData; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java index 5e05c99..dddc7a0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidNature.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.project; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.builders.PostCompilerBuilder; import com.android.ide.eclipse.adt.internal.build.builders.PreCompilerBuilder; import com.android.ide.eclipse.adt.internal.build.builders.ResourceManagerBuilder; @@ -127,7 +127,7 @@ public class AndroidNature implements IProjectNature { // Adding the java nature after the android one, would place the java builder before the // android builders. addNatureToProjectDescription(project, JavaCore.NATURE_ID, monitor); - addNatureToProjectDescription(project, AndroidConstants.NATURE_DEFAULT, monitor); + addNatureToProjectDescription(project, AdtConstants.NATURE_DEFAULT, monitor); } /** @@ -151,7 +151,7 @@ public class AndroidNature implements IProjectNature { String[] newNatures = new String[natures.length + 1]; // Android natures always come first. - if (natureId.equals(AndroidConstants.NATURE_DEFAULT)) { + if (natureId.equals(AdtConstants.NATURE_DEFAULT)) { System.arraycopy(natures, 0, newNatures, 1, natures.length); newNatures[0] = natureId; } else { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java index 6485f96..8c4d08d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/BaseProjectHelper.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.project; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; @@ -110,6 +110,26 @@ public final class BaseProjectHelper { */ public final static IMarker markResource(IResource resource, String markerId, String message, int lineNumber, int severity) { + return markResource(resource, markerId, message, lineNumber, -1, -1, severity); + } + + /** + * Adds a marker to a file on a specific line, for a specific range of text. This + * methods catches thrown {@link CoreException}, and returns null instead. + * + * @param resource the resource to be marked + * @param markerId The id of the marker to add. + * @param message the message associated with the mark + * @param lineNumber the line number where to put the mark. If line is < 1, it puts + * the marker on line 1, + * @param startOffset the beginning offset of the marker (relative to the beginning of + * the document, not the line), or -1 for no range + * @param endOffset the ending offset of the marker + * @param severity the severity of the marker. + * @return the IMarker that was added or null if it failed to add one. + */ + public final static IMarker markResource(IResource resource, String markerId, + String message, int lineNumber, int startOffset, int endOffset, int severity) { try { IMarker marker = resource.createMarker(markerId); marker.setAttribute(IMarker.MESSAGE, message); @@ -125,6 +145,11 @@ public final class BaseProjectHelper { marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); } + if (startOffset != -1) { + marker.setAttribute(IMarker.CHAR_START, startOffset); + marker.setAttribute(IMarker.CHAR_END, endOffset); + } + // on Windows, when adding a marker to a project, it takes a refresh for the marker // to show. In order to fix this we're forcing a refresh of elements receiving // markers (and only the element, not its children), to force the marker display. @@ -402,7 +427,7 @@ public final class BaseProjectHelper { // check if it's an android project based on its nature try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT)) { if (filter == null || filter.accept(project)) { androidProjectList.add(javaProject); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java index 3b0622a..3d0e088 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AndroidPrintStream; import com.android.ide.eclipse.adt.internal.build.BuildHelper; import com.android.ide.eclipse.adt.internal.build.DexException; @@ -124,7 +124,7 @@ public final class ExportHelper { // tmp file for the packaged resource file. To not disturb the incremental builders // output, all intermediary files are created in tmp files. - File resourceFile = File.createTempFile(TEMP_PREFIX, AndroidConstants.DOT_RES); + File resourceFile = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_RES); resourceFile.deleteOnExit(); // package the resources. @@ -139,7 +139,7 @@ public final class ExportHelper { // Step 2. Convert the byte code to Dalvik bytecode // tmp file for the packaged resource file. - File dexFile = File.createTempFile(TEMP_PREFIX, AndroidConstants.DOT_DEX); + File dexFile = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_DEX); dexFile.deleteOnExit(); ProjectState state = Sdk.getProjectState(project); @@ -163,7 +163,7 @@ public final class ExportHelper { String[] projectOutputs = helper.getProjectOutputs(); // create a jar from the output of these projects - File inputJar = File.createTempFile(TEMP_PREFIX, AndroidConstants.DOT_JAR); + File inputJar = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_JAR); inputJar.deleteOnExit(); JarOutputStream jos = new JarOutputStream(new FileOutputStream(inputJar)); @@ -180,7 +180,7 @@ public final class ExportHelper { null /*resourceMarker*/); // destination file for proguard - File obfuscatedJar = File.createTempFile(TEMP_PREFIX, AndroidConstants.DOT_JAR); + File obfuscatedJar = File.createTempFile(TEMP_PREFIX, AdtConstants.DOT_JAR); obfuscatedJar.deleteOnExit(); // run proguard @@ -274,7 +274,7 @@ public final class ExportHelper { IPath binLocation = outputFolder.getLocation(); // make the full path to the package - String fileName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; + String fileName = project.getName() + AdtConstants.DOT_ANDROID_PACKAGE; File file = new File(binLocation.toOSString() + File.separator + fileName); @@ -348,7 +348,7 @@ public final class ExportHelper { } else if (file.isFile()) { // check the extension String name = file.getName(); - if (name.toLowerCase().endsWith(AndroidConstants.DOT_CLASS) == false) { + if (name.toLowerCase().endsWith(AdtConstants.DOT_CLASS) == false) { return; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java index 7ca244c..394338b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/FolderDecorator.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.SdkConstants; @@ -54,7 +54,7 @@ public class FolderDecorator implements ILightweightLabelDecorator { } try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT)) { // check the folder is directly under the project. if (folder.getParent().getType() == IResource.PROJECT) { String name = folder.getName(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java index 91e2380..ca988a2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ProjectHelper.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.project; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.sdklib.SdkConstants; import com.android.sdklib.xml.ManifestData; @@ -116,11 +116,11 @@ public final class ProjectHelper { public static String getJavaDocPath(String javaDocOSLocation) { // first thing we do is convert the \ into / String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$ - AndroidConstants.WS_SEP); + AdtConstants.WS_SEP); // then we add file: at the beginning for unix path, and file:/ for non // unix path - if (javaDoc.startsWith(AndroidConstants.WS_SEP)) { + if (javaDoc.startsWith(AdtConstants.WS_SEP)) { return "file:" + javaDoc; //$NON-NLS-1$ } @@ -365,11 +365,11 @@ public final class ProjectHelper { if (checkCompilerCompliance(javaProject) != COMPILER_COMPLIANCE_OK) { // setup the preferred compiler compliance level. javaProject.setOption(JavaCore.COMPILER_COMPLIANCE, - AndroidConstants.COMPILER_COMPLIANCE_PREFERRED); + AdtConstants.COMPILER_COMPLIANCE_PREFERRED); javaProject.setOption(JavaCore.COMPILER_SOURCE, - AndroidConstants.COMPILER_COMPLIANCE_PREFERRED); + AdtConstants.COMPILER_COMPLIANCE_PREFERRED); javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, - AndroidConstants.COMPILER_COMPLIANCE_PREFERRED); + AdtConstants.COMPILER_COMPLIANCE_PREFERRED); // clean the project to make sure we recompile try { @@ -402,7 +402,7 @@ public final class ProjectHelper { for (IProject p : projects) { if (p.isOpen()) { try { - if (p.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (p.hasNature(AdtConstants.NATURE_DEFAULT) == false) { // ignore non android projects continue; } @@ -451,16 +451,16 @@ public final class ProjectHelper { String[] natures = description.getNatureIds(); // if the android nature is not the first one, we reorder them - if (AndroidConstants.NATURE_DEFAULT.equals(natures[0]) == false) { + if (AdtConstants.NATURE_DEFAULT.equals(natures[0]) == false) { // look for the index for (int i = 0 ; i < natures.length ; i++) { - if (AndroidConstants.NATURE_DEFAULT.equals(natures[i])) { + if (AdtConstants.NATURE_DEFAULT.equals(natures[i])) { // if we try to just reorder the array in one pass, this doesn't do // anything. I guess JDT check that we are actually adding/removing nature. // So, first we'll remove the android nature, and then add it back. // remove the android nature - removeNature(project, AndroidConstants.NATURE_DEFAULT); + removeNature(project, AdtConstants.NATURE_DEFAULT); // now add it back at the first index. description = project.getDescription(); @@ -469,7 +469,7 @@ public final class ProjectHelper { String[] newNatures = new String[natures.length + 1]; // first one is android - newNatures[0] = AndroidConstants.NATURE_DEFAULT; + newNatures[0] = AdtConstants.NATURE_DEFAULT; // next the rest that was before the android nature System.arraycopy(natures, 0, newNatures, 1, natures.length); @@ -675,7 +675,7 @@ public final class ProjectHelper { * @return true if the option value is supproted. */ private static boolean checkCompliance(String optionValue) { - for (String s : AndroidConstants.COMPILER_COMPLIANCE) { + for (String s : AdtConstants.COMPILER_COMPLIANCE) { if (s != null && s.equals(optionValue)) { return true; } @@ -691,10 +691,10 @@ public final class ProjectHelper { */ public static String getApkFilename(IProject project, String config) { if (config != null) { - return project.getName() + "-" + config + AndroidConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$ + return project.getName() + "-" + config + AdtConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$ } - return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; + return project.getName() + AdtConstants.DOT_ANDROID_PACKAGE; } /** @@ -718,7 +718,7 @@ public final class ProjectHelper { //Verify that the project has also the Android Nature try { - if (!androidJavaProject.getProject().hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (!androidJavaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) { continue; } } catch (CoreException e) { @@ -750,7 +750,7 @@ public final class ProjectHelper { // get the package path - String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; + String packageName = project.getName() + AdtConstants.DOT_ANDROID_PACKAGE; IResource r = outputLocation.findMember(packageName); // check the package is present @@ -772,7 +772,7 @@ public final class ProjectHelper { * is missing. */ public static IFile getManifest(IProject project) { - IResource r = project.findMember(AndroidConstants.WS_SEP + IResource r = project.findMember(AdtConstants.WS_SEP + SdkConstants.FN_ANDROID_MANIFEST_XML); if (r == null || r.exists() == false || (r instanceof IFile) == false) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java index d5f6f15..7cd0161 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/XmlErrorHandler.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.project; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.sdklib.xml.AndroidManifestParser.ManifestErrorHandler; import org.eclipse.core.resources.IFile; @@ -97,7 +97,7 @@ public class XmlErrorHandler extends DefaultHandler implements ManifestErrorHand public void warning(SAXParseException exception) throws SAXException { if (mFile != null) { BaseProjectHelper.markResource(mFile, - AndroidConstants.MARKER_XML, + AdtConstants.MARKER_XML, exception.getMessage(), exception.getLineNumber(), IMarker.SEVERITY_WARNING); @@ -125,7 +125,7 @@ public class XmlErrorHandler extends DefaultHandler implements ManifestErrorHand if (mFile != null) { BaseProjectHelper.markResource(mFile, - AndroidConstants.MARKER_XML, + AdtConstants.MARKER_XML, message, lineNumber, IMarker.SEVERITY_ERROR); @@ -156,14 +156,14 @@ public class XmlErrorHandler extends DefaultHandler implements ManifestErrorHand // mark the file IMarker marker = BaseProjectHelper.markResource(getFile(), - AndroidConstants.MARKER_ANDROID, result, line, IMarker.SEVERITY_ERROR); + AdtConstants.MARKER_ANDROID, result, line, IMarker.SEVERITY_ERROR); // add custom attributes to be used by the manifest editor. if (marker != null) { try { - marker.setAttribute(AndroidConstants.MARKER_ATTR_TYPE, - AndroidConstants.MARKER_ATTR_TYPE_ACTIVITY); - marker.setAttribute(AndroidConstants.MARKER_ATTR_CLASS, className); + marker.setAttribute(AdtConstants.MARKER_ATTR_TYPE, + AdtConstants.MARKER_ATTR_TYPE_ACTIVITY); + marker.setAttribute(AdtConstants.MARKER_ATTR_CLASS, className); } catch (CoreException e) { } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java index f6677f8..fe3c014 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java @@ -277,7 +277,7 @@ public class AndroidDocumentChange extends DocumentChange { if (name != null) {
String newValue;
if (combinePackage) {
- newValue = RefactoringUtil.getNewValue(getAppPackage(), name, newName);
+ newValue = AndroidManifest.extractActivityName(newName, getAppPackage());
} else {
newValue = newName;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java index 43cb09d..3a8348d 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java @@ -16,8 +16,9 @@ package com.android.ide.eclipse.adt.internal.refactoring.core;
+import com.android.AndroidConstants;
import com.android.ide.common.layout.LayoutConstants;
-import com.android.ide.eclipse.adt.AndroidConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
@@ -203,7 +204,7 @@ public class AndroidPackageRenameParticipant extends AndroidRenameParticipant { IJavaProject javaProject = (IJavaProject) mPackageFragment
.getAncestor(IJavaElement.JAVA_PROJECT);
IProject project = javaProject.getProject();
- IResource manifestResource = project.findMember(AndroidConstants.WS_SEP
+ IResource manifestResource = project.findMember(AdtConstants.WS_SEP
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
if (manifestResource == null || !manifestResource.exists()
@@ -297,7 +298,7 @@ public class AndroidPackageRenameParticipant extends AndroidRenameParticipant { IResource resource = layoutMembers[j];
if (resource instanceof IFolder
&& resource.exists()
- && resource.getName().startsWith(SdkConstants.FD_LAYOUT)) {
+ && resource.getName().startsWith(AndroidConstants.FD_RES_LAYOUT)) {
IFolder layoutFolder = (IFolder) resource;
IResource[] members = layoutFolder.members();
for (int i = 0; i < members.length; i++) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java index e7c3e1a..735c595 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java @@ -16,8 +16,9 @@ package com.android.ide.eclipse.adt.internal.refactoring.core;
+import com.android.AndroidConstants;
import com.android.ide.common.layout.LayoutConstants;
-import com.android.ide.eclipse.adt.AndroidConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
@@ -174,7 +175,7 @@ public class AndroidTypeMoveParticipant extends MoveParticipant { IType type = (IType) element;
IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
IProject project = javaProject.getProject();
- IResource manifestResource = project.findMember(AndroidConstants.WS_SEP
+ IResource manifestResource = project.findMember(AdtConstants.WS_SEP
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
if (manifestResource == null || !manifestResource.exists()
@@ -232,7 +233,7 @@ public class AndroidTypeMoveParticipant extends MoveParticipant { private void addLayoutChanges(IProject project, String className) {
try {
IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
- IFolder layoutFolder = resFolder.getFolder(SdkConstants.FD_LAYOUT);
+ IFolder layoutFolder = resFolder.getFolder(AndroidConstants.FD_RES_LAYOUT);
IResource[] members = layoutFolder.members();
for (int i = 0; i < members.length; i++) {
IResource member = members[i];
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java index dc393e3..b80125a 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java @@ -16,8 +16,9 @@ package com.android.ide.eclipse.adt.internal.refactoring.core;
+import com.android.AndroidConstants;
import com.android.ide.common.layout.LayoutConstants;
-import com.android.ide.eclipse.adt.AndroidConstants;
+import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
@@ -129,7 +130,7 @@ public class AndroidTypeRenameParticipant extends AndroidRenameParticipant { IType type = (IType) element;
IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
IProject project = javaProject.getProject();
- IResource manifestResource = project.findMember(AndroidConstants.WS_SEP
+ IResource manifestResource = project.findMember(AdtConstants.WS_SEP
+ SdkConstants.FN_ANDROID_MANIFEST_XML);
if (manifestResource == null || !manifestResource.exists()
@@ -193,7 +194,7 @@ public class AndroidTypeRenameParticipant extends AndroidRenameParticipant { private void addLayoutChanges(IProject project, String className) {
try {
IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
- IFolder layoutFolder = resFolder.getFolder(SdkConstants.FD_LAYOUT);
+ IFolder layoutFolder = resFolder.getFolder(AndroidConstants.FD_RES_LAYOUT);
IResource[] members = layoutFolder.members();
for (int i = 0; i < members.length; i++) {
IResource member = members[i];
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java index 409cf72..adc4d5a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java @@ -41,49 +41,6 @@ public class RefactoringUtil { private static boolean sRefactorAppPackage = false; /** - * Returns the new class name combined with a package name - * the oldName and newName are class names as found in the manifest - * (for instance with a leading dot or with a single element, - * that needs to be recombined with a package name) - * - * @param javaPackage the package name - * @param oldName the old name - * @param newName the new name - * - * @return the new name - */ - public static String getNewValue(String javaPackage, String oldName, String newName) { - if (oldName == null || oldName.length() == 0) { - return null; - } - if (javaPackage == null || javaPackage.length() == 0) { - return null; - } - if (newName == null || newName.length() == 0) { - return null; - } - if (!newName.startsWith(javaPackage + ".")) { //$NON-NLS-1$ - return newName; - } else if (newName.length() > (javaPackage.length() + 1)) { - String value = newName.substring(javaPackage.length() + 1); - return value; - } - boolean startWithDot = (oldName.charAt(0) == '.'); - boolean hasDot = (oldName.indexOf('.') != -1); - if (startWithDot || !hasDot) { - - if (startWithDot) { - return "." + newName; - } else { - int lastPeriod = newName.lastIndexOf("."); - return newName.substring(lastPeriod + 1); - } - } else { - return newName; - } - } - - /** * Releases SSE read model; saves SSE model if exists edit model * Called in dispose method of refactoring change classes * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java index ffa4089..e005f1c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringAction.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.refactorings.extractstring; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -165,7 +165,7 @@ public class ExtractStringAction implements IWorkbenchWindowActionDelegate { if (file.exists()) { IProject proj = file.getProject(); try { - if (proj != null && proj.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (proj != null && proj.hasNature(AdtConstants.NATURE_DEFAULT)) { return file; } } catch (CoreException e) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java index 8dab07e..dee8407 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringInputPage.java @@ -17,11 +17,12 @@ package com.android.ide.eclipse.adt.internal.refactorings.extractstring; -import com.android.ide.eclipse.adt.AndroidConstants; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; +import com.android.AndroidConstants; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode; +import com.android.resources.ResourceFolderType; import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFolder; @@ -83,10 +84,10 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage "/res/[a-z][a-zA-Z0-9_-]+/[^.]+\\.xml"); //$NON-NLS-1$ /** Absolute destination folder root, e.g. "/res/" */ private static final String RES_FOLDER_ABS = - AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; + AdtConstants.WS_RESOURCES + AdtConstants.WS_SEP; /** Relative destination folder root, e.g. "res/" */ private static final String RES_FOLDER_REL = - SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP; private static final String DEFAULT_RES_FILE_PATH = "/res/values/strings.xml"; //$NON-NLS-1$ @@ -470,7 +471,7 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage mConfigSelector.getConfiguration(mTempConfig); StringBuffer sb = new StringBuffer(RES_FOLDER_ABS); sb.append(mTempConfig.getFolderName(ResourceFolderType.VALUES)); - sb.append(AndroidConstants.WS_SEP); + sb.append(AdtConstants.WS_SEP); String newPath = sb.toString(); @@ -571,12 +572,12 @@ class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage if (wsFolderPath.startsWith(RES_FOLDER_ABS)) { wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length()); - int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR); + int pos = wsFolderPath.indexOf(AdtConstants.WS_SEP_CHAR); if (pos >= 0) { wsFolderPath = wsFolderPath.substring(0, pos); } - String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP); + String[] folderSegments = wsFolderPath.split(AndroidConstants.RES_QUALIFIER_SEP); if (folderSegments.length > 0) { String folderName = folderSegments[0]; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java index 1a41caf..955a0b2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java @@ -18,7 +18,7 @@ package com.android.ide.eclipse.adt.internal.refactorings.extractstring; import static com.android.ide.common.layout.LayoutConstants.STRING_PREFIX; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ReferenceAttributeDescriptor; @@ -26,7 +26,7 @@ import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.Resour import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import com.android.sdklib.SdkConstants; import com.android.sdklib.xml.ManifestData; @@ -424,7 +424,7 @@ public class ExtractStringRefactoring extends Refactoring { } // Check this a Layout XML file and get the selection and its context. - if (mFile != null && AndroidConstants.EXT_XML.equals(mFile.getFileExtension())) { + if (mFile != null && AdtConstants.EXT_XML.equals(mFile.getFileExtension())) { // Currently we only support Android resource XML files, so they must have a path // similar to @@ -1016,7 +1016,7 @@ public class ExtractStringRefactoring extends Refactoring { // Add all /res folders (technically we don't need to process /res/values // XML files that contain resources/string elements, but it's easier to // not filter them out.) - IFolder f = mProject.getFolder(AndroidConstants.WS_RESOURCES); + IFolder f = mProject.getFolder(AdtConstants.WS_RESOURCES); if (f.exists()) { try { mFolders.addAll( @@ -1055,7 +1055,7 @@ public class ExtractStringRefactoring extends Refactoring { if (res.exists() && !res.isDerived() && res instanceof IFile) { IFile file = (IFile) res; // Must have an XML extension - if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) { + if (AdtConstants.EXT_XML.equals(file.getFileExtension())) { IPath p = file.getFullPath(); // And not be either paths we want to filter out if ((mFilterPath1 != null && mFilterPath1.equals(p)) || @@ -1101,7 +1101,7 @@ public class ExtractStringRefactoring extends Refactoring { SubMonitor monitor) { TextFileChange xmlChange = new TextFileChange(getName(), targetXml); - xmlChange.setTextType(AndroidConstants.EXT_XML); + xmlChange.setTextType(AdtConstants.EXT_XML); String error = ""; //$NON-NLS-1$ TextEdit edit = null; @@ -1478,7 +1478,7 @@ public class ExtractStringRefactoring extends Refactoring { HashSet<IFile> files = new HashSet<IFile>(); files.add(sourceFile); - if (allConfigurations && AndroidConstants.EXT_XML.equals(sourceFile.getFileExtension())) { + if (allConfigurations && AdtConstants.EXT_XML.equals(sourceFile.getFileExtension())) { IPath path = sourceFile.getFullPath(); if (path.segmentCount() == 4 && path.segment(1).equals(SdkConstants.FD_RESOURCES)) { IProject project = sourceFile.getProject(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java index 9097f97..3df35bc 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.internal.refactorings.renamepackage; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; import com.android.sdklib.SdkConstants; import com.android.sdklib.xml.AndroidManifest; @@ -153,7 +153,7 @@ class ApplicationPackageNameRefactoring extends Refactoring { ImportRewrite irw = ImportRewrite.create(cu, true); irw.addImport(mNewPackageName.getFullyQualifiedName() + '.' - + AndroidConstants.FN_RESOURCE_BASE); + + AdtConstants.FN_RESOURCE_BASE); try { rewrittenImports.addChild( irw.rewriteImports(null) ); @@ -210,14 +210,14 @@ class ApplicationPackageNameRefactoring extends Refactoring { } TextFileChange xmlChange = new TextFileChange("XML resource file edit", file); - xmlChange.setTextType(AndroidConstants.EXT_XML); + xmlChange.setTextType(AdtConstants.EXT_XML); MultiTextEdit multiEdit = new MultiTextEdit(); ArrayList<TextEditGroup> editGroups = new ArrayList<TextEditGroup>(); - final String oldAppNamespaceString = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, + final String oldAppNamespaceString = String.format(AdtConstants.NS_CUSTOM_RESOURCES, mOldPackageName.getFullyQualifiedName()); - final String newAppNamespaceString = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, + final String newAppNamespaceString = String.format(AdtConstants.NS_CUSTOM_RESOURCES, mNewPackageName.getFullyQualifiedName()); // Prepare the change set @@ -301,7 +301,7 @@ class ApplicationPackageNameRefactoring extends Refactoring { } TextFileChange xmlChange = new TextFileChange("Make Manifest edits", file); - xmlChange.setTextType(AndroidConstants.EXT_XML); + xmlChange.setTextType(AdtConstants.EXT_XML); MultiTextEdit multiEdit = new MultiTextEdit(); ArrayList<TextEditGroup> editGroups = new ArrayList<TextEditGroup>(); @@ -417,7 +417,7 @@ class ApplicationPackageNameRefactoring extends Refactoring { public boolean visit(IResource resource) throws CoreException { if (resource instanceof IFile) { IFile file = (IFile) resource; - if (AndroidConstants.EXT_JAVA.equals(file.getFileExtension())) { + if (AdtConstants.EXT_JAVA.equals(file.getFileExtension())) { ICompilationUnit icu = JavaCore.createCompilationUnitFrom(file); @@ -430,7 +430,7 @@ class ApplicationPackageNameRefactoring extends Refactoring { edit.addChild(text_edit); TextFileChange text_file_change = new TextFileChange(file.getName(), file); - text_file_change.setTextType(AndroidConstants.EXT_JAVA); + text_file_change.setTextType(AdtConstants.EXT_JAVA); text_file_change.setEdit(edit); mChanges.add(text_file_change); } @@ -438,7 +438,7 @@ class ApplicationPackageNameRefactoring extends Refactoring { // XXX Partially taken from ExtractStringRefactoring.java // Check this a Layout XML file and get the selection and // its context. - } else if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) { + } else if (AdtConstants.EXT_XML.equals(file.getFileExtension())) { if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(file.getName())) { @@ -510,7 +510,7 @@ class ApplicationPackageNameRefactoring extends Refactoring { QualifiedName qualifiedImportName = (QualifiedName) importName; if (qualifiedImportName.getName().getIdentifier() - .equals(AndroidConstants.FN_RESOURCE_BASE)) { + .equals(AdtConstants.FN_RESOURCE_BASE)) { mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName, null); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/CyclicDependencyValidator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/CyclicDependencyValidator.java new file mode 100644 index 0000000..625adc6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/CyclicDependencyValidator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.resources; + +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.dialogs.IInputValidator; + +import java.util.Collection; + +/** A validator which checks for cyclic dependencies */ +public class CyclicDependencyValidator implements IInputValidator { + private final Collection<String> mInvalidIds; + + private CyclicDependencyValidator(Collection<String> invalid) { + this.mInvalidIds = invalid; + } + + public String isValid(String newText) { + if (mInvalidIds.contains(newText)) { + return String.format( + "Cyclic include, not valid", + newText); + } + return null; + } + + /** + * Creates a validator which ensures that the chosen id is not for a layout that is + * directly or indirectly including the given layout. Used to avoid cyclic + * dependencies when offering layouts to be included within a given file, etc. + * + * @param file the target file that candidate layouts should not directly or + * indirectly include + * @return a validator which checks whether resource ids are valid or whether they + * could result in a cyclic dependency + */ + public static IInputValidator create(IFile file) { + IProject project = file.getProject(); + IncludeFinder includeFinder = IncludeFinder.get(project); + final Collection<String> invalid = + includeFinder.getInvalidIncludes(file); + + return new CyclicDependencyValidator(invalid); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/IResourceRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/IResourceRepository.java deleted file mode 100644 index 1abd9eb..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/IResourceRepository.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.resources; - -import com.android.resources.ResourceType; - -/** - * A repository of resources. This allows access to the resource by {@link ResourceType}. - */ -public interface IResourceRepository { - - /** - * Returns the present {@link ResourceType}s in the project. - * @return an array containing all the type of resources existing in the project. - */ - public abstract ResourceType[] getAvailableResourceTypes(); - - /** - * Returns an array of the existing resource for the specified type. - * @param type the type of the resources to return - */ - public abstract ResourceItem[] getResources(ResourceType type); - - /** - * Returns whether resources of the specified type are present. - * @param type the type of the resources to check. - */ - public abstract boolean hasResources(ResourceType type); - - /** - * Returns whether the repository is a system repository. - */ - public abstract boolean isSystemRepository(); - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java index 327bd89..467ae49 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java @@ -16,27 +16,431 @@ package com.android.ide.eclipse.adt.internal.resources; +import static com.android.AndroidConstants.FD_RES_VALUES; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_STYLE; +import static com.android.ide.common.resources.ResourceResolver.PREFIX_STYLE; +import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG; +import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP; +import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; +import static com.android.sdklib.SdkConstants.FD_RESOURCES; + +import com.android.ide.common.resources.ResourceDeltaKind; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.DockModeQualifier; +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.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +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.VersionQualifier; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.VisualRefactoring; +import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; +import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; +import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; +import com.android.resources.FolderTypeRelationship; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.swt.graphics.Image; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +/** + * Helper class to deal with SWT specifics for the resources. + */ +@SuppressWarnings("restriction") // XML model public class ResourceHelper { + private final static Map<Class<?>, Image> sIconMap = new HashMap<Class<?>, Image>( + FolderConfiguration.getQualifierCount()); + + static { + IconFactory factory = IconFactory.getInstance(); + sIconMap.put(CountryCodeQualifier.class, factory.getIcon("mcc")); //$NON-NLS-1$ + sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc")); //$NON-NLS-1$ + sIconMap.put(LanguageQualifier.class, factory.getIcon("language")); //$NON-NLS-1$ + sIconMap.put(RegionQualifier.class, factory.getIcon("region")); //$NON-NLS-1$ + sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size")); //$NON-NLS-1$ + sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio")); //$NON-NLS-1$ + sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation")); //$NON-NLS-1$ + sIconMap.put(DockModeQualifier.class, factory.getIcon("dockmode")); //$NON-NLS-1$ + sIconMap.put(NightModeQualifier.class, factory.getIcon("nightmode")); //$NON-NLS-1$ + sIconMap.put(PixelDensityQualifier.class, factory.getIcon("dpi")); //$NON-NLS-1$ + sIconMap.put(TouchScreenQualifier.class, factory.getIcon("touch")); //$NON-NLS-1$ + sIconMap.put(KeyboardStateQualifier.class, factory.getIcon("keyboard")); //$NON-NLS-1$ + sIconMap.put(TextInputMethodQualifier.class, factory.getIcon("text_input")); //$NON-NLS-1$ + sIconMap.put(NavigationStateQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ + sIconMap.put(NavigationMethodQualifier.class, factory.getIcon("navpad")); //$NON-NLS-1$ + sIconMap.put(ScreenDimensionQualifier.class, factory.getIcon("dimension")); //$NON-NLS-1$ + sIconMap.put(VersionQualifier.class, factory.getIcon("version")); //$NON-NLS-1$ + } + + /** + * Returns the icon for the qualifier. + */ + public static Image getIcon(Class<? extends ResourceQualifier> theClass) { + return sIconMap.get(theClass); + } + + /** + * Returns a {@link ResourceDeltaKind} from an {@link IResourceDelta} value. + * @param kind a {@link IResourceDelta} integer constant. + * @return a matching {@link ResourceDeltaKind} or null. + * + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + */ + public static ResourceDeltaKind getResourceDeltaKind(int kind) { + switch (kind) { + case IResourceDelta.ADDED: + return ResourceDeltaKind.ADDED; + case IResourceDelta.REMOVED: + return ResourceDeltaKind.REMOVED; + case IResourceDelta.CHANGED: + return ResourceDeltaKind.CHANGED; + } + + return null; + } + + /** + * Return the resource type of the given url, and the resource name + * + * @param url the resource url to be parsed + * @return a pair of the resource type and the resource name + */ + public static Pair<ResourceType,String> parseResource(String url) { + if (!url.startsWith("@")) { //$NON-NLS-1$ + return null; + } + int typeEnd = url.indexOf('/', 1); + if (typeEnd == -1) { + return null; + } + int nameBegin = typeEnd + 1; + + // Skip @ and @+ + int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$ + + int colon = url.lastIndexOf(':', typeEnd); + if (colon != -1) { + typeBegin = colon + 1; + } + String typeName = url.substring(typeBegin, typeEnd); + ResourceType type = ResourceType.getEnum(typeName); + if (type == null) { + return null; + } + String name = url.substring(nameBegin); + + return Pair.of(type, name); + } + + /** + * Is this a resource that can be defined in any file within the "values" folder? + * <p> + * Some resource types can be defined <b>both</b> as a separate XML file as well + * as defined within a value XML file. This method will return true for these types + * as well. In other words, a ResourceType can return true for both + * {@link #isValueBasedResourceType} and {@link #isFileBasedResourceType}. + * + * @param type the resource type to check + * @return true if the given resource type can be represented as a value under the + * values/ folder + */ + public static boolean isValueBasedResourceType(ResourceType type) { + List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); + for (ResourceFolderType folderType : folderTypes) { + if (folderType == ResourceFolderType.VALUES) { + return true; + } + } + + return false; + } + + /** + * Is this a resource that is defined in a file named by the resource plus the XML + * extension? + * <p> + * Some resource types can be defined <b>both</b> as a separate XML file as well as + * defined within a value XML file along with other properties. This method will + * return true for these resource types as well. In other words, a ResourceType can + * return true for both {@link #isValueBasedResourceType} and + * {@link #isFileBasedResourceType}. + * + * @param type the resource type to check + * @return true if the given resource type is stored in a file named by the resource + */ + public static boolean isFileBasedResourceType(ResourceType type) { + List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); + for (ResourceFolderType folderType : folderTypes) { + if (folderType != ResourceFolderType.VALUES) { + return true; + } + } + + return false; + } + + /** + * Returns true if this class can create the given resource + * + * @param resource the resource to be created + * @return true if the {@link #createResource} method can create this resource + */ + public static boolean canCreateResource(String resource) { + // Cannot create framework resources + if (resource.startsWith('@' + ANDROID_PKG + ':')) { + return false; + } + + Pair<ResourceType,String> parsed = parseResource(resource); + if (parsed != null) { + ResourceType type = parsed.getFirst(); + String name = parsed.getSecond(); + + // Make sure the name is valid + ResourceNameValidator validator = + ResourceNameValidator.create(false, (Set<String>) null /* existing */, type); + if (validator.isValid(name) != null) { + return false; + } + + // We can create all value types + if (isValueBasedResourceType(type)) { + return true; + } + + // We can create -some- file-based types - those supported by the New XML wizard: + for (ResourceFolderType folderType : FolderTypeRelationship.getRelatedFolders(type)) { + if (NewXmlFileWizard.canCreateXmlFile(folderType)) { + return true; + } + } + } + + return false; + } + + /** Creates a file-based resource, like a layout. Used by {@link #createResource} */ + private static Pair<IFile,IRegion> createFileResource(IProject project, ResourceType type, + String name) { + + ResourceFolderType folderType = null; + for (ResourceFolderType f : FolderTypeRelationship.getRelatedFolders(type)) { + if (NewXmlFileWizard.canCreateXmlFile(f)) { + folderType = f; + break; + } + } + if (folderType == null) { + return null; + } + + // Find "dimens.xml" file in res/values/ (or corresponding name for other + // value types) + IPath projectPath = new Path(FD_RESOURCES + WS_SEP + folderType.getName() + WS_SEP + + name + '.' + EXT_XML); + IFile file = project.getFile(projectPath); + return NewXmlFileWizard.createXmlFile(project, file, folderType); + } + /** - * Returns a formatted string usable in an XML to use the specified {@link ResourceItem}. - * @param resourceItem The resource item. - * @param system Whether this is a system resource or a project resource. - * @return a string in the format @[type]/[name] + * Creates a resource of a given type, name and (if applicable) value + * + * @param project the project to contain the resource + * @param type the type of resource + * @param name the name of the resource + * @param value the value of the resource, if it is a value-type resource + * @return a pair of the file containing the resource and a region where the value + * appears */ - public static String getXmlString(ResourceType type, ResourceItem resourceItem, - boolean system) { - if (type == ResourceType.ID && resourceItem instanceof IIdResourceItem) { - IIdResourceItem idResource = (IIdResourceItem)resourceItem; - if (idResource.isDeclaredInline()) { - return (system?"@android:":"@+") + type.getName() + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + public static Pair<IFile,IRegion> createResource(IProject project, ResourceType type, + String name, String value) { + if (!isValueBasedResourceType(type)) { + return createFileResource(project, type, name); + } + + // Find "dimens.xml" file in res/values/ (or corresponding name for other + // value types) + String fileName = type.getName() + 's'; + String projectPath = FD_RESOURCES + WS_SEP + FD_RES_VALUES + WS_SEP + + fileName + '.' + EXT_XML; + Object editRequester = project; + IResource member = project.findMember(projectPath); + if (member != null) { + if (member instanceof IFile) { + IFile file = (IFile) member; + // File exists: Must add item to the XML + IModelManager manager = StructuredModelManager.getModelManager(); + IStructuredModel model = null; + try { + model = manager.getExistingModelForEdit(file); + if (model == null) { + model = manager.getModelForEdit(file); + } + if (model instanceof IDOMModel) { + model.beginRecording(editRequester, String.format("Add %1$s", + type.getDisplayName())); + IDOMModel domModel = (IDOMModel) model; + Document document = domModel.getDocument(); + Element root = document.getDocumentElement(); + IStructuredDocument structuredDocument = model.getStructuredDocument(); + Node lastElement = null; + NodeList childNodes = root.getChildNodes(); + String indent = null; + for (int i = childNodes.getLength() - 1; i >= 0; i--) { + Node node = childNodes.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + lastElement = node; + indent = AndroidXmlEditor.getIndent(structuredDocument, node); + break; + } + } + if (indent == null || indent.length() == 0) { + indent = " "; //$NON-NLS-1$ + } + Node nextChild = lastElement != null ? lastElement.getNextSibling() : null; + Text indentNode = document.createTextNode('\n' + indent); + root.insertBefore(indentNode, nextChild); + Element element = document.createElement(Hyperlinks.getTagName(type)); + element.setAttribute(NAME_ATTR, name); + root.insertBefore(element, nextChild); + Text valueNode = document.createTextNode(value); + element.appendChild(valueNode); + model.save(); + IndexedRegion domRegion = VisualRefactoring.getRegion(valueNode); + int startOffset = domRegion.getStartOffset(); + int length = domRegion.getLength(); + IRegion region = new Region(startOffset, length); + return Pair.of(file, region); + } + } catch (Exception e) { + AdtPlugin.log(e, "Cannot access XML value model"); + } finally { + if (model != null) { + model.endRecording(editRequester); + model.releaseFromEdit(); + } + } + } + + return null; + } else { + // No such file exists: just create it + String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(prolog); + + String root = ResourcesDescriptors.ROOT_ELEMENT; + sb.append('<').append(root).append('>').append('\n'); + sb.append(" "); //$NON-NLS-1$ + sb.append('<'); + sb.append(type.getName()); + sb.append(" name=\""); //$NON-NLS-1$ + sb.append(name); + sb.append('"'); + sb.append('>'); + int start = sb.length(); + sb.append(value); + int end = sb.length(); + sb.append('<').append('/'); + sb.append(type.getName()); + sb.append(">\n"); //$NON-NLS-1$ + sb.append('<').append('/').append(root).append('>').append('\n'); + String result = sb.toString(); + String error = null; + try { + byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$ + InputStream stream = new ByteArrayInputStream(buf); + IFile file = project.getFile(new Path(projectPath)); + file.create(stream, true /*force*/, null /*progress*/); + IRegion region = new Region(start, end - start); + return Pair.of(file, region); + } catch (UnsupportedEncodingException e) { + error = e.getMessage(); + } catch (CoreException e) { + error = e.getMessage(); } + + error = String.format("Failed to generate %1$s: %2$s", name, error); + AdtPlugin.displayError("New Android XML File", error); } + return null; + } - return (system?"@android:":"@") + type.getName() + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** + * Returns the theme name to be shown for theme styles, e.g. for "@style/Theme" it + * returns "Theme" + * + * @param style a theme style string + * @return the user visible theme name + */ + public static String styleToTheme(String style) { + if (style.startsWith(PREFIX_STYLE)) { + style = style.substring(PREFIX_STYLE.length()); + } else if (style.startsWith(PREFIX_ANDROID_STYLE)) { + style = style.substring(PREFIX_ANDROID_STYLE.length()); + } + return style; } + /** + * Returns the layout resource name for the given layout file, e.g. for + * /res/layout/foo.xml returns foo. + * + * @param layoutFile the layout file whose name we want to look up + * @return the layout name + */ + public static String getLayoutName(IFile layoutFile) { + String layoutName = layoutFile.getName(); + int dotIndex = layoutName.indexOf('.'); + if (dotIndex != -1) { + layoutName = layoutName.substring(0, dotIndex); + } + return layoutName; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceItem.java deleted file mode 100644 index c340ffe..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceItem.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 com.android.ide.eclipse.adt.internal.resources; - -/** - * Base class representing a Resource Item, as returned by a {@link IResourceRepository}. - */ -public class ResourceItem implements Comparable<ResourceItem> { - - private final String mName; - - /** - * Constructs a new ResourceItem - * @param name the name of the resource as it appears in the XML and R.java files. - */ - public ResourceItem(String name) { - mName = name; - } - - /** - * Returns the name of the resource item. - */ - public final String getName() { - return mName; - } - - /** - * Compares the {@link ResourceItem} to another. - * @param other the ResourceItem to be compared to. - */ - public int compareTo(ResourceItem other) { - return mName.compareTo(other.mName); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ResourceNameValidator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceNameValidator.java index cc1aa25..4c1127a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ResourceNameValidator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceNameValidator.java @@ -14,15 +14,16 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.wizards.newxmlfile; +package com.android.ide.eclipse.adt.internal.resources; -import static com.android.ide.eclipse.adt.AndroidConstants.DOT_XML; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResourceItem; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; import org.eclipse.core.resources.IProject; @@ -30,6 +31,7 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.JavaConventions; import org.eclipse.jface.dialogs.IInputValidator; +import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -40,12 +42,21 @@ public class ResourceNameValidator implements IInputValidator { /** Set of existing names to check for conflicts with */ private Set<String> mExisting; + /** + * True if the resource name being considered is a "file" based resource (where the + * resource name is the actual file name, rather than just a value attribute inside an + * XML file name of arbitrary name + */ + private boolean mIsFileType; + /** If true, allow .xml as a name suffix */ private boolean mAllowXmlExtension; - private ResourceNameValidator(boolean allowXmlExtension, Set<String> existing) { + private ResourceNameValidator(boolean allowXmlExtension, Set<String> existing, + boolean isFileType) { mAllowXmlExtension = allowXmlExtension; mExisting = existing; + mIsFileType = isFileType; } public String isValid(String newText) { @@ -59,7 +70,7 @@ public class ResourceNameValidator implements IInputValidator { newText = newText.substring(0, newText.length() - DOT_XML.length()); } - if (newText.indexOf('.') != -1 && !newText.endsWith(AndroidConstants.DOT_XML)) { + if (newText.indexOf('.') != -1 && !newText.endsWith(AdtConstants.DOT_XML)) { return String.format("The filename must end with %1$s.", DOT_XML); } @@ -71,7 +82,19 @@ public class ResourceNameValidator implements IInputValidator { for (int i = 1, n = newText.length(); i < n; i++) { char c = newText.charAt(i); if (!Character.isJavaIdentifierPart(c)) { - return String.format("%1$c is not a valid resource name character", c); + return String.format("'%1$c' is not a valid resource name character", c); + } + } + + if (mIsFileType) { + // AAPT only allows lowercase+digits+_: + // "%s: Invalid file name: must contain only [a-z0-9_.]"," + for (int i = 0, n = newText.length(); i < n; i++) { + char c = newText.charAt(i); + if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')) { + return String.format( + "File-based resource names must contain only lowercase a-z, 0-9, or _."); + } } } @@ -97,10 +120,13 @@ public class ResourceNameValidator implements IInputValidator { * * @param allowXmlExtension if true, allow .xml to be entered as a suffix for the * resource name + * @param type the resource type of the resource name being validated * @return a new {@link ResourceNameValidator} */ - public static ResourceNameValidator create(boolean allowXmlExtension) { - return new ResourceNameValidator(allowXmlExtension, null); + public static ResourceNameValidator create(boolean allowXmlExtension, + ResourceFolderType type) { + boolean isFileType = type != ResourceFolderType.VALUES; + return new ResourceNameValidator(allowXmlExtension, null, isFileType); } /** @@ -110,10 +136,13 @@ public class ResourceNameValidator implements IInputValidator { * resource name * @param existing An optional set of names that already exist (and therefore will not * be considered valid if entered as the new name) + * @param type the resource type of the resource name being validated * @return a new {@link ResourceNameValidator} */ - public static ResourceNameValidator create(boolean allowXmlExtension, Set<String> existing) { - return new ResourceNameValidator(allowXmlExtension, existing); + public static ResourceNameValidator create(boolean allowXmlExtension, Set<String> existing, + ResourceType type) { + boolean isFileType = ResourceHelper.isFileBasedResourceType(type); + return new ResourceNameValidator(allowXmlExtension, existing, isFileType); } /** @@ -130,11 +159,12 @@ public class ResourceNameValidator implements IInputValidator { Set<String> existing = new HashSet<String>(); ResourceManager manager = ResourceManager.getInstance(); ProjectResources projectResources = manager.getProjectResources(project); - ProjectResourceItem[] resources = projectResources.getResources(type); - for (ProjectResourceItem resource : resources) { - existing.add(resource.getName()); + Collection<ResourceItem> items = projectResources.getResourceItemsOfType(type); + for (ResourceItem item : items) { + existing.add(item.getName()); } - return new ResourceNameValidator(allowXmlExtension, existing); + boolean isFileType = ResourceHelper.isFileBasedResourceType(type); + return new ResourceNameValidator(allowXmlExtension, existing, isFileType); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java index 295fd4c..172f471 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java @@ -16,8 +16,9 @@ package com.android.ide.eclipse.adt.internal.resources.manager; +import com.android.ide.common.resources.IntArrayWrapper; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; @@ -77,7 +78,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi * @see IFileListener#fileChanged */ public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - if (file.getName().equals(AndroidConstants.FN_COMPILED_RESOURCE_CLASS)) { + if (file.getName().equals(AdtConstants.FN_COMPILED_RESOURCE_CLASS)) { loadAndParseRClass(file.getProject()); } } @@ -115,7 +116,7 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi public void projectOpenedWithWorkspace(IProject project) { try { // check this is an android project - if (project.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT)) { loadAndParseRClass(project); } } catch (CoreException e) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ConfigurableResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ConfigurableResourceItem.java deleted file mode 100644 index 2a998f8..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ConfigurableResourceItem.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.resources.manager; - -/** - * Represents a resource item that can exist in multiple "alternate" versions. - */ -public class ConfigurableResourceItem extends ProjectResourceItem { - - /** - * Constructs a new Resource Item. - * @param name the name of the resource as it appears in the XML and R.java files. - */ - public ConfigurableResourceItem(String name) { - super(name); - } - - /** - * Returns if the resource item has at least one non-default configuration. - */ - public boolean hasAlternates() { - for (ResourceFile file : mFiles) { - if (file.getFolder().getConfiguration().isDefault() == false) { - return true; - } - } - - return false; - } - - /** - * Returns whether the resource has a default version, with no qualifier. - */ - public boolean hasDefault() { - for (ResourceFile file : mFiles) { - if (file.getFolder().getConfiguration().isDefault()) { - return true; - } - } - - // We only want to return false if there's no default and more than 0 items. - return (mFiles.size() == 0); - } - - /** - * Returns the number of alternate versions of this resource. - */ - public int getAlternateCount() { - int count = 0; - for (ResourceFile file : mFiles) { - if (file.getFolder().getConfiguration().isDefault() == false) { - count++; - } - } - - return count; - } - - /* - * (non-Javadoc) - * Returns whether the item can be edited directly (ie it does not have alternate versions). - */ - @Override - public boolean isEditableDirectly() { - return hasAlternates() == false; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/FolderTypeRelationship.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/FolderTypeRelationship.java deleted file mode 100644 index 168f7fb..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/FolderTypeRelationship.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.resources.manager; - -import com.android.resources.ResourceType; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -/** - * This class gives access to the bi directional relationship between {@link ResourceType} and - * {@link ResourceFolderType}. - */ -public final class FolderTypeRelationship { - - private final static HashMap<ResourceType, ResourceFolderType[]> mTypeToFolderMap = - new HashMap<ResourceType, ResourceFolderType[]>(); - - private final static HashMap<ResourceFolderType, ResourceType[]> mFolderToTypeMap = - new HashMap<ResourceFolderType, ResourceType[]>(); - - // generate the relationships. - static { - HashMap<ResourceType, List<ResourceFolderType>> typeToFolderMap = - new HashMap<ResourceType, List<ResourceFolderType>>(); - - HashMap<ResourceFolderType, List<ResourceType>> folderToTypeMap = - new HashMap<ResourceFolderType, List<ResourceType>>(); - - add(ResourceType.ANIM, ResourceFolderType.ANIM, typeToFolderMap, folderToTypeMap); - add(ResourceType.ANIMATOR, ResourceFolderType.ANIMATOR, typeToFolderMap, folderToTypeMap); - add(ResourceType.ARRAY, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.ATTR, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.BOOL, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.COLOR, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.COLOR, ResourceFolderType.COLOR, typeToFolderMap, folderToTypeMap); - add(ResourceType.DECLARE_STYLEABLE, ResourceFolderType.VALUES, typeToFolderMap, - folderToTypeMap); - add(ResourceType.DIMEN, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.DRAWABLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.DRAWABLE, ResourceFolderType.DRAWABLE, typeToFolderMap, folderToTypeMap); - add(ResourceType.FRACTION, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.ID, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.INTEGER, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.INTERPOLATOR, ResourceFolderType.INTERPOLATOR, typeToFolderMap, - folderToTypeMap); - add(ResourceType.LAYOUT, ResourceFolderType.LAYOUT, typeToFolderMap, folderToTypeMap); - add(ResourceType.MENU, ResourceFolderType.MENU, typeToFolderMap, folderToTypeMap); - add(ResourceType.MIPMAP, ResourceFolderType.MIPMAP, typeToFolderMap, folderToTypeMap); - add(ResourceType.PLURALS, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.PUBLIC, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.RAW, ResourceFolderType.RAW, typeToFolderMap, folderToTypeMap); - add(ResourceType.STRING, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.STYLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.STYLEABLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.XML, ResourceFolderType.XML, typeToFolderMap, folderToTypeMap); - - optimize(typeToFolderMap, folderToTypeMap); - } - - /** - * Returns a list of {@link ResourceType}s that can be generated from files inside a folder - * of the specified type. - * @param folderType The folder type. - * @return an array of {@link ResourceType} - */ - public static ResourceType[] getRelatedResourceTypes(ResourceFolderType folderType) { - ResourceType[] array = mFolderToTypeMap.get(folderType); - if (array != null) { - return array; - } - return new ResourceType[0]; - } - - /** - * Returns a list of {@link ResourceFolderType} that can contain files generating resources - * of the specified type. - * @param resType the type of resource. - * @return an array of {@link ResourceFolderType} - */ - public static ResourceFolderType[] getRelatedFolders(ResourceType resType) { - ResourceFolderType[] array = mTypeToFolderMap.get(resType); - if (array != null) { - return array; - } - return new ResourceFolderType[0]; - } - - /** - * Returns true if the {@link ResourceType} and the {@link ResourceFolderType} values match. - * @param resType the resource type. - * @param folderType the folder type. - * @return true if files inside the folder of the specified {@link ResourceFolderType} - * could generate a resource of the specified {@link ResourceType} - */ - public static boolean match(ResourceType resType, ResourceFolderType folderType) { - ResourceFolderType[] array = mTypeToFolderMap.get(resType); - - if (array != null && array.length > 0) { - for (ResourceFolderType fType : array) { - if (fType == folderType) { - return true; - } - } - } - - return false; - } - - /** - * Adds a {@link ResourceType} - {@link ResourceFolderType} relationship. this indicates that - * a file in the folder can generate a resource of the specified type. - * @param type The resourceType - * @param folder The {@link ResourceFolderType} - * @param folderToTypeMap - * @param typeToFolderMap - */ - private static void add(ResourceType type, ResourceFolderType folder, - HashMap<ResourceType, List<ResourceFolderType>> typeToFolderMap, - HashMap<ResourceFolderType, List<ResourceType>> folderToTypeMap) { - // first we add the folder to the list associated with the type. - List<ResourceFolderType> folderList = typeToFolderMap.get(type); - if (folderList == null) { - folderList = new ArrayList<ResourceFolderType>(); - typeToFolderMap.put(type, folderList); - } - if (folderList.indexOf(folder) == -1) { - folderList.add(folder); - } - - // now we add the type to the list associated with the folder. - List<ResourceType> typeList = folderToTypeMap.get(folder); - if (typeList == null) { - typeList = new ArrayList<ResourceType>(); - folderToTypeMap.put(folder, typeList); - } - if (typeList.indexOf(type) == -1) { - typeList.add(type); - } - } - - /** - * Optimize the map to contains array instead of lists (since the api returns arrays) - * @param typeToFolderMap - * @param folderToTypeMap - */ - private static void optimize(HashMap<ResourceType, List<ResourceFolderType>> typeToFolderMap, - HashMap<ResourceFolderType, List<ResourceType>> folderToTypeMap) { - Set<ResourceType> types = typeToFolderMap.keySet(); - for (ResourceType type : types) { - List<ResourceFolderType> list = typeToFolderMap.get(type); - mTypeToFolderMap.put(type, list.toArray(new ResourceFolderType[list.size()])); - } - - Set<ResourceFolderType> folders = folderToTypeMap.keySet(); - for (ResourceFolderType folder : folders) { - List<ResourceType> list = folderToTypeMap.get(folder); - mFolderToTypeMap.put(folder, list.toArray(new ResourceType[list.size()])); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java index c6d16d4..cc615ec 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java @@ -16,6 +16,8 @@ package com.android.ide.eclipse.adt.internal.resources.manager; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdResourceItem.java deleted file mode 100644 index 8b142fb..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IdResourceItem.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 com.android.ide.eclipse.adt.internal.resources.manager; - -import com.android.ide.eclipse.adt.internal.resources.IIdResourceItem; -import com.android.resources.ResourceType; - -/** - * Represents a resource item of type {@link ResourceType#ID} - */ -public class IdResourceItem extends ProjectResourceItem implements IIdResourceItem { - - private final boolean mIsDeclaredInline; - - /** - * Constructs a new ResourceItem. - * @param name the name of the resource as it appears in the XML and R.java files. - * @param isDeclaredInline Whether this id was declared inline. - */ - IdResourceItem(String name, boolean isDeclaredInline) { - super(name); - mIsDeclaredInline = isDeclaredInline; - } - - /* - * (non-Javadoc) - * Returns whether the ID resource has been declared inline inside another resource XML file. - */ - public boolean isDeclaredInline() { - return mIsDeclaredInline; - } - - /* (non-Javadoc) - * Returns whether the item can be edited (ie, the id was not declared inline). - */ - @Override - public boolean isEditableDirectly() { - return !mIsDeclaredInline; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java index 0deb89c..dc543b8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectClassLoader.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.resources.manager; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.BuildHelper; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; @@ -262,7 +262,7 @@ public final class ProjectClassLoader extends ClassLoader { IPath path = e.getPath(); // check the name ends with .jar - if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { + if (AdtConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { boolean local = false; IResource resource = wsRoot.findMember(path); if (resource != null && resource.exists() && diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResourceItem.java deleted file mode 100644 index 845a974..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResourceItem.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.android.ide.eclipse.adt.internal.resources.manager; - -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.resources.ResourceType; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Base class for Resource Item coming from an Android Project. - */ -public abstract class ProjectResourceItem extends ResourceItem { - - private final static Comparator<ResourceFile> sComparator = new Comparator<ResourceFile>() { - public int compare(ResourceFile file1, ResourceFile file2) { - // get both FolderConfiguration and compare them - FolderConfiguration fc1 = file1.getFolder().getConfiguration(); - FolderConfiguration fc2 = file2.getFolder().getConfiguration(); - - return fc1.compareTo(fc2); - } - }; - - /** - * List of files generating this ResourceItem. - */ - protected final ArrayList<ResourceFile> mFiles = new ArrayList<ResourceFile>(); - - /** - * Constructs a new ResourceItem. - * @param name the name of the resource as it appears in the XML and R.java files. - */ - public ProjectResourceItem(String name) { - super(name); - } - - /** - * Returns whether the resource item is editable directly. - * <p/> - * This is typically the case for resources that don't have alternate versions, or resources - * of type {@link ResourceType#ID} that aren't declared inline. - */ - public abstract boolean isEditableDirectly(); - - /** - * Adds a new version of this resource item, by adding its {@link ResourceFile}. - * @param file the {@link ResourceFile} object. - */ - protected void add(ResourceFile file) { - mFiles.add(file); - } - - /** - * Reset the item by emptying its version list. - */ - protected void reset() { - mFiles.clear(); - } - - /** - * Returns the sorted list of {@link ResourceItem} objects for this resource item. - */ - public ResourceFile[] getSourceFileArray() { - ArrayList<ResourceFile> list = new ArrayList<ResourceFile>(); - list.addAll(mFiles); - - Collections.sort(list, sComparator); - - return list.toArray(new ResourceFile[list.size()]); - } - - /** - * Returns the list of {@link ResourceItem} objects for this resource item. - */ - public List<ResourceFile> getSourceFileList() { - return Collections.unmodifiableList(mFiles); - } - - - /** - * Replaces the content of the receiver with the ResourceItem received as parameter. - * @param item - */ - protected void replaceWith(ProjectResourceItem item) { - mFiles.clear(); - mFiles.addAll(item.mFiles); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java index 64a36e1..ec8b717 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java @@ -17,48 +17,44 @@ package com.android.ide.eclipse.adt.internal.resources.manager; import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; +import com.android.ide.common.resources.InlineResourceItem; +import com.android.ide.common.resources.IntArrayWrapper; +import com.android.ide.common.resources.ResourceFolder; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.io.IFolderWrapper; import com.android.resources.ResourceType; -import com.android.sdklib.io.IAbstractFolder; import com.android.util.Pair; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; import java.util.Map.Entry; /** - * Represents the resources of a project. This is a file view of the resources, with handling - * for the alternate resource types. For a compiled view use CompiledResources. + * Represents the resources of a project. + * On top of the regular {@link ResourceRepository} features it provides: + *<ul> + *<li>configured resources contain the resources coming from the libraries.</li> + *<li>resolution to and from resource integer (compiled value in R.java).</li> + *<li>handles resource integer for non existing values of type ID. This is used when rendering.</li> + *<li>layouts that have no been saved yet. This is handled by generating dynamic IDs + * on the fly.</li> + *</ul> */ -public class ProjectResources implements IResourceRepository { - private final static int DYNAMIC_ID_SEED_START = 0; // this should not conflict with any - // project IDs that start at a much higher - // value - - private final Map<ResourceFolderType, List<ResourceFolder>> mFolderMap = - new EnumMap<ResourceFolderType, List<ResourceFolder>>(ResourceFolderType.class); - - private final Map<ResourceType, List<ProjectResourceItem>> mResourceMap = - new EnumMap<ResourceType, List<ProjectResourceItem>>(ResourceType.class); +public class ProjectResources extends ResourceRepository { + // project resources are defined as 0x7FXX#### where XX is the resource type (layout, drawable, + // etc...). Using FF as the type allows for 255 resource types before we get a collision + // which should be fine. + private final static int DYNAMIC_ID_SEED_START = 0x7fff0000; /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */ private Map<ResourceType, Map<String, Integer>> mResourceValueMap; @@ -67,352 +63,39 @@ public class ProjectResources implements IResourceRepository { /** Map of (int[], name) for styleable resources coming from R.java */ private Map<IntArrayWrapper, String> mStyleableValueToNameMap; + /** + * This list is used by {@link #getResourceId(String, String)} when the resource + * query is an ID that doesn't exist (for example for ID automatically generated in + * layout files that are not saved yet). + */ private final Map<String, Integer> mDynamicIds = new HashMap<String, Integer>(); + private final Map<Integer, String> mRevDynamicIds = new HashMap<Integer, String>(); private int mDynamicSeed = DYNAMIC_ID_SEED_START; - /** Cached list of {@link IdResourceItem}. This is mix of IdResourceItem created by - * {@link MultiResourceFile} for ids coming from XML files under res/values and - * {@link IdResourceItem} created manually, from the list coming from R.java */ - private final List<IdResourceItem> mIdResourceList = new ArrayList<IdResourceItem>(); - - private final boolean mIsFrameworkRepository; private final IProject mProject; - private final IntArrayWrapper mWrapper = new IntArrayWrapper(null); - /** * Makes a ProjectResources for a given <var>project</var>. * @param project the project. */ public ProjectResources(IProject project) { - mIsFrameworkRepository = false; + super(false /*isFrameworkRepository*/); mProject = project; } /** - * Makes a ProjectResource for a framework repository. + * Returns the resources values matching a given {@link FolderConfiguration}, this will + * include library dependency. * - * @see #isSystemRepository() - */ - public ProjectResources() { - mIsFrameworkRepository = true; - mProject = null; - } - - /** - * Returns whether this ProjectResources is for a project or for a framework. - */ - public boolean isSystemRepository() { - return mIsFrameworkRepository; - } - - /** - * Adds a Folder Configuration to the project. - * @param type The resource type. - * @param config The resource configuration. - * @param folder The workspace folder object. - * @return the {@link ResourceFolder} object associated to this folder. - */ - protected ResourceFolder add(ResourceFolderType type, FolderConfiguration config, - IAbstractFolder folder) { - // get the list for the resource type - List<ResourceFolder> list = mFolderMap.get(type); - - if (list == null) { - list = new ArrayList<ResourceFolder>(); - - ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); - list.add(cf); - - mFolderMap.put(type, list); - - return cf; - } - - // look for an already existing folder configuration. - for (ResourceFolder cFolder : list) { - if (cFolder.mConfiguration.equals(config)) { - // config already exist. Nothing to be done really, besides making sure - // the IFolder object is up to date. - cFolder.mFolder = folder; - return cFolder; - } - } - - // If we arrive here, this means we didn't find a matching configuration. - // So we add one. - ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); - list.add(cf); - - return cf; - } - - /** - * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}. - * @param type The type of the folder - * @param folder the IFolder object. - * @return the {@link ResourceFolder} that was removed, or null if no matches were found. - */ - protected ResourceFolder removeFolder(ResourceFolderType type, IFolder folder) { - // get the list of folders for the resource type. - List<ResourceFolder> list = mFolderMap.get(type); - - if (list != null) { - int count = list.size(); - for (int i = 0 ; i < count ; i++) { - ResourceFolder resFolder = list.get(i); - // this is only used for Eclipse stuff so we know it's an IFolderWrapper - IFolderWrapper wrapper = (IFolderWrapper) resFolder.getFolder(); - if (wrapper.getIFolder().equals(folder)) { - // we found the matching ResourceFolder. we need to remove it. - list.remove(i); - - // we now need to invalidate this resource type. - // The easiest way is to touch one of the other folders of the same type. - if (list.size() > 0) { - list.get(0).touch(); - } else { - // if the list is now empty, and we have a single ResouceType out of this - // ResourceFolderType, then we are done. - // However, if another ResourceFolderType can generate similar ResourceType - // than this, we need to update those ResourceTypes as well. - // For instance, if the last "drawable-*" folder is deleted, we need to - // refresh the ResourceItem associated with ResourceType.DRAWABLE. - // Those can be found in ResourceFolderType.DRAWABLE but also in - // ResourceFolderType.VALUES. - // If we don't find a single folder to touch, then it's fine, as the top - // level items (the list of generated resource types) is not cached - // (for now) - - // get the lists of ResourceTypes generated by this ResourceFolderType - ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( - type); - - // for each of those, make sure to find one folder to touch so that the - // list of ResourceItem associated with the type is rebuilt. - for (ResourceType resType : resTypes) { - // get the list of folder that can generate this type - ResourceFolderType[] folderTypes = - FolderTypeRelationship.getRelatedFolders(resType); - - // we only need to touch one folder in any of those (since it's one - // folder per type, not per folder type). - for (ResourceFolderType folderType : folderTypes) { - List<ResourceFolder> resFolders = mFolderMap.get(folderType); - - if (resFolders != null && resFolders.size() > 0) { - resFolders.get(0).touch(); - break; - } - } - } - } - - // we're done updating/touching, we can stop - return resFolder; - } - } - } - - return null; - } - - - /** - * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}. - * @param type The {@link ResourceFolderType} - */ - public List<ResourceFolder> getFolders(ResourceFolderType type) { - return mFolderMap.get(type); - } - - /* (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getAvailableResourceTypes() - */ - public ResourceType[] getAvailableResourceTypes() { - ArrayList<ResourceType> list = new ArrayList<ResourceType>(); - - // For each key, we check if there's a single ResourceType match. - // If not, we look for the actual content to give us the resource type. - - for (ResourceFolderType folderType : mFolderMap.keySet()) { - ResourceType types[] = FolderTypeRelationship.getRelatedResourceTypes(folderType); - if (types.length == 1) { - // before we add it we check if it's not already present, since a ResourceType - // could be created from multiple folders, even for the folders that only create - // one type of resource (drawable for instance, can be created from drawable/ and - // values/) - if (list.indexOf(types[0]) == -1) { - list.add(types[0]); - } - } else { - // there isn't a single resource type out of this folder, so we look for all - // content. - List<ResourceFolder> folders = mFolderMap.get(folderType); - if (folders != null) { - for (ResourceFolder folder : folders) { - Collection<ResourceType> folderContent = folder.getResourceTypes(); - - // then we add them, but only if they aren't already in the list. - for (ResourceType folderResType : folderContent) { - if (list.indexOf(folderResType) == -1) { - list.add(folderResType); - } - } - } - } - } - } - - // in case ResourceType.ID haven't been added yet because there's no id defined - // in XML, we check on the list of compiled id resources. - if (list.indexOf(ResourceType.ID) == -1 && mResourceValueMap != null) { - Map<String, Integer> map = mResourceValueMap.get(ResourceType.ID); - if (map != null && map.size() > 0) { - list.add(ResourceType.ID); - } - } - - // at this point the list is full of ResourceType defined in the files. - // We need to sort it. - Collections.sort(list); - - return list.toArray(new ResourceType[list.size()]); - } - - /* (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getResources(com.android.ide.eclipse.common.resources.ResourceType) - */ - public ProjectResourceItem[] getResources(ResourceType type) { - checkAndUpdate(type); - - if (type == ResourceType.ID) { - synchronized (mIdResourceList) { - return mIdResourceList.toArray(new ProjectResourceItem[mIdResourceList.size()]); - } - } - - List<ProjectResourceItem> items = mResourceMap.get(type); - - return items.toArray(new ProjectResourceItem[items.size()]); - } - - /* (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.IResourceRepository#hasResources(com.android.ide.eclipse.common.resources.ResourceType) - */ - public boolean hasResources(ResourceType type) { - checkAndUpdate(type); - - if (type == ResourceType.ID) { - synchronized (mIdResourceList) { - return mIdResourceList.size() > 0; - } - } - - List<ProjectResourceItem> items = mResourceMap.get(type); - return (items != null && items.size() > 0); - } - - /** - * Returns the {@link ResourceFolder} associated with a {@link IFolder}. - * @param folder The {@link IFolder} object. - * @return the {@link ResourceFolder} or null if it was not found. - */ - public ResourceFolder getResourceFolder(IFolder folder) { - for (List<ResourceFolder> list : mFolderMap.values()) { - for (ResourceFolder resFolder : list) { - // this is only used for Eclipse stuff so we know it's an IFolderWrapper - IFolderWrapper wrapper = (IFolderWrapper) resFolder.getFolder(); - if (wrapper.getIFolder().equals(folder)) { - return resFolder; - } - } - } - - return null; - } - - /** - * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and - * configuration. - * <p/>This only works with files generating one resource named after the file (for instance, - * layouts, bitmap based drawable, xml, anims). - * @return the matching file or <code>null</code> if no match was found. - */ - public ResourceFile getMatchingFile(String name, ResourceFolderType type, - FolderConfiguration config) { - // get the folders for the given type - List<ResourceFolder> folders = mFolderMap.get(type); - - // look for folders containing a file with the given name. - ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>(); - - // remove the folders that do not have a file with the given name. - for (int i = 0 ; i < folders.size(); i++) { - ResourceFolder folder = folders.get(i); - - if (folder.hasFile(name) == true) { - matchingFolders.add(folder); - } - } - - // from those, get the folder with a config matching the given reference configuration. - Resource match = findMatchingConfiguredResource(matchingFolders, config); - - // do we have a matching folder? - if (match instanceof ResourceFolder) { - // get the ResourceFile from the filename - return ((ResourceFolder)match).getFile(name); - } - - return null; - } - - /** - * Returns the list of source files for a given resource. - * Optionally, if a {@link FolderConfiguration} is given, then only the best - * match for this config is returned. - * - * @param type the type of the resource. - * @param name the name of the resource. - * @param referenceConfig an optional config for which only the best match will be returned. - * - * @return a list of files generating this resource or null if it was not found. - */ - public List<ResourceFile> getSourceFiles(ResourceType type, String name, - FolderConfiguration referenceConfig) { - - ProjectResourceItem[] resources = getResources(type); - - for (ProjectResourceItem item : resources) { - if (name.equals(item.getName())) { - if (referenceConfig != null) { - Resource match = findMatchingConfiguredResource(item.getSourceFileList(), - referenceConfig); - if (match instanceof ResourceFile) { - ArrayList<ResourceFile> list = new ArrayList<ResourceFile>(); - list.add((ResourceFile) match); - return list; - } - - return null; - } - return item.getSourceFileList(); - } - } - - return null; - } - - /** - * Returns the resources values matching a given {@link FolderConfiguration}. * @param referenceConfig the configuration that each value must match. + * @return a map with guaranteed to contain an entry for each {@link ResourceType} */ + @Override public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources( FolderConfiguration referenceConfig) { - Map<ResourceType, Map<String, ResourceValue>> map = + Map<ResourceType, Map<String, ResourceValue>> resultMap = new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class); // if the project contains libraries, we need to add the libraries resources here @@ -433,25 +116,27 @@ public class ProjectResources implements IResourceRepository { ProjectResources libRes = resMgr.getProjectResources(library); if (libRes != null) { - // make sure they are loaded - libRes.loadAll(); + // get the library resources, and only the library, not the dependencies + // so call doGetConfiguredResources() directly. + Map<ResourceType, Map<String, ResourceValue>> libMap = + libRes.doGetConfiguredResources(referenceConfig); // we don't want to simply replace the whole map, but instead merge the // content of any sub-map - Map<ResourceType, Map<String, ResourceValue>> libMap = - libRes.getConfiguredResources(referenceConfig); + for (Entry<ResourceType, Map<String, ResourceValue>> libEntry : + libMap.entrySet()) { - for (Entry<ResourceType, Map<String, ResourceValue>> entry : libMap.entrySet()) { // get the map currently in the result map for this resource type - Map<String, ResourceValue> tempMap = map.get(entry.getKey()); + Map<String, ResourceValue> tempMap = resultMap.get(libEntry.getKey()); if (tempMap == null) { // since there's no current map for this type, just add the map // directly coming from the library resources - map.put(entry.getKey(), entry.getValue()); + resultMap.put(libEntry.getKey(), libEntry.getValue()); } else { // already a map for this type. add the resources from the - // library. - tempMap.putAll(entry.getValue()); + // library, this will override existing value, which is why + // we loop in a specific library order. + tempMap.putAll(libEntry.getValue()); } } } @@ -460,81 +145,59 @@ public class ProjectResources implements IResourceRepository { } // now the project resources themselves. - // Don't blindly fill the map, instead check if there are sub-map already present - // due to library resources. - - // special case for Id since there's a mix of compiled id (declared inline) and id declared - // in the XML files. - if (mIdResourceList.size() > 0) { - Map<String, ResourceValue> idMap = map.get(ResourceType.ID); - - if (idMap == null) { - idMap = new HashMap<String, ResourceValue>(); - map.put(ResourceType.ID, idMap); - } - for (IdResourceItem id : mIdResourceList) { - // FIXME: cache the ResourceValue! - idMap.put(id.getName(), new ResourceValue(ResourceType.ID, id.getName(), - mIsFrameworkRepository)); - } - - } - - Set<ResourceType> keys = mResourceMap.keySet(); - for (ResourceType key : keys) { - // we don't process ID resources since we already did it above. - if (key != ResourceType.ID) { - // get the local results - Map<String, ResourceValue> localResMap = getConfiguredResource(key, - referenceConfig); - - // check if a map for this type already exists - Map<String, ResourceValue> resMap = map.get(key); - if (resMap == null) { - // just use the local results. - map.put(key, localResMap); - } else { - // add to the library results. - resMap.putAll(localResMap); - } + Map<ResourceType, Map<String, ResourceValue>> thisProjectMap = + doGetConfiguredResources(referenceConfig); + + // now merge the maps. + for (Entry<ResourceType, Map<String, ResourceValue>> entry : thisProjectMap.entrySet()) { + ResourceType type = entry.getKey(); + Map<String, ResourceValue> typeMap = resultMap.get(type); + if (typeMap == null) { + resultMap.put(type, entry.getValue()); + } else { + typeMap.putAll(entry.getValue()); } } - return map; + return resultMap; } /** - * Loads all the resources. Essentially this forces to load the values from the - * {@link ResourceFile} objects to make sure they are up to date and loaded - * in {@link #mResourceMap}. + * Returns the {@link ResourceFolder} associated with a {@link IFolder}. + * @param folder The {@link IFolder} object. + * @return the {@link ResourceFolder} or null if it was not found. + * + * @see ResourceRepository#getResourceFolder(com.android.io.IAbstractFolder) */ - public void loadAll() { - // gets all the resource types available. - ResourceType[] types = getAvailableResourceTypes(); - - // loop on them and load them - for (ResourceType type: types) { - checkAndUpdate(type); - } + public ResourceFolder getResourceFolder(IFolder folder) { + return getResourceFolder(new IFolderWrapper(folder)); } /** * Resolves a compiled resource id into the resource name and type - * @param id - * @return an array of 2 strings { name, type } or null if the id could not be resolved + * @param id the resource integer id. + * @return a {@link Pair} of 2 strings { name, type } or null if the id could not be resolved */ - public Pair<ResourceType, String> resolveResourceValue(int id) { + public Pair<ResourceType, String> resolveResourceId(int id) { + Pair<ResourceType, String> result = null; if (mResIdValueToNameMap != null) { - return mResIdValueToNameMap.get(id); + result = mResIdValueToNameMap.get(id); } - return null; + if (result == null) { + String name = mRevDynamicIds.get(id); + if (name != null) { + result = Pair.of(ResourceType.ID, name); + } + } + + return result; } /** - * Resolves a compiled resource id of type int[] into the resource name. + * Resolves a compiled styleable id of type int[] into the styleable name. */ - public String resolveResourceValue(int[] id) { + public String resolveStyleable(int[] id) { if (mStyleableValueToNameMap != null) { mWrapper.set(id); return mStyleableValueToNameMap.get(mWrapper); @@ -544,12 +207,12 @@ public class ProjectResources implements IResourceRepository { } /** - * Returns the value of a resource by its type and name. + * Returns the integer id of a resource given its type and name. * <p/>If the resource is of type {@link ResourceType#ID} and does not exist in the * internal map, then new id values are dynamically generated (and stored so that queries * with the same names will return the same value). */ - public Integer getResourceValue(ResourceType type, String name) { + public Integer getResourceId(ResourceType type, String name) { if (mResourceValueMap != null) { Map<String, Integer> map = mResourceValueMap.get(type); if (map != null) { @@ -570,327 +233,41 @@ public class ProjectResources implements IResourceRepository { } /** - * Returns the sorted list of languages used in the resources. - */ - public SortedSet<String> getLanguages() { - SortedSet<String> set = new TreeSet<String>(); - - Collection<List<ResourceFolder>> folderList = mFolderMap.values(); - for (List<ResourceFolder> folderSubList : folderList) { - for (ResourceFolder folder : folderSubList) { - FolderConfiguration config = folder.getConfiguration(); - LanguageQualifier lang = config.getLanguageQualifier(); - if (lang != null) { - set.add(lang.getShortDisplayValue()); - } - } - } - - return set; - } - - /** - * Returns the sorted list of regions used in the resources with the given language. - * @param currentLanguage the current language the region must be associated with. - */ - public SortedSet<String> getRegions(String currentLanguage) { - SortedSet<String> set = new TreeSet<String>(); - - Collection<List<ResourceFolder>> folderList = mFolderMap.values(); - for (List<ResourceFolder> folderSubList : folderList) { - for (ResourceFolder folder : folderSubList) { - FolderConfiguration config = folder.getConfiguration(); - - // get the language - LanguageQualifier lang = config.getLanguageQualifier(); - if (lang != null && lang.getShortDisplayValue().equals(currentLanguage)) { - RegionQualifier region = config.getRegionQualifier(); - if (region != null) { - set.add(region.getShortDisplayValue()); - } - } - } - } - - return set; - } - - /** * Resets the list of dynamic Ids. This list is used by - * {@link #getResourceValue(String, String)} when the resource query is an ID that doesn't - * exist (for example for ID automatically generated in layout files that are not saved. + * {@link #getResourceId(String, String)} when the resource query is an ID that doesn't + * exist (for example for ID automatically generated in layout files that are not saved yet.) * <p/>This method resets those dynamic ID and must be called whenever the actual list of IDs * change. */ public void resetDynamicIds() { synchronized (mDynamicIds) { mDynamicIds.clear(); + mRevDynamicIds.clear(); mDynamicSeed = DYNAMIC_ID_SEED_START; } } - /** - * Returns a map of (resource name, resource value) for the given {@link ResourceType}. - * <p/>The values returned are taken from the resource files best matching a given - * {@link FolderConfiguration}. - * @param type the type of the resources. - * @param referenceConfig the configuration to best match. - */ - private Map<String, ResourceValue> getConfiguredResource(ResourceType type, - FolderConfiguration referenceConfig) { - // get the resource item for the given type - List<ProjectResourceItem> items = mResourceMap.get(type); - - // create the map - HashMap<String, ResourceValue> map = new HashMap<String, ResourceValue>(); - - for (ProjectResourceItem item : items) { - // get the source files generating this resource - List<ResourceFile> list = item.getSourceFileList(); - - // look for the best match for the given configuration - Resource match = findMatchingConfiguredResource(list, referenceConfig); - - if (match instanceof ResourceFile) { - ResourceFile matchResFile = (ResourceFile)match; - - // get the value of this configured resource. - ResourceValue value = matchResFile.getValue(type, item.getName()); - - if (value != null) { - map.put(item.getName(), value); - } - } - } - - return map; - } - - /** - * Returns the best matching {@link Resource}. - * @param resources the list of {@link Resource} to choose from. - * @param referenceConfig the {@link FolderConfiguration} to match. - * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match - */ - private Resource findMatchingConfiguredResource(List<? extends Resource> resources, - FolderConfiguration referenceConfig) { - // - // 1: eliminate resources that contradict the reference configuration - // 2: pick next qualifier type - // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4. - // 4: eliminate resources that don't use this qualifier. - // 5: if more than one resource left, go back to 2. - // - // The precedence of the qualifiers is more important than the number of qualifiers that - // exactly match the device. - - // 1: eliminate resources that contradict - ArrayList<Resource> matchingResources = new ArrayList<Resource>(); - for (int i = 0 ; i < resources.size(); i++) { - Resource res = resources.get(i); - - if (res.getConfiguration().isMatchFor(referenceConfig)) { - matchingResources.add(res); - } - } - - // if there is only one match, just take it - if (matchingResources.size() == 1) { - return matchingResources.get(0); - } else if (matchingResources.size() == 0) { - return null; - } - - // 2. Loop on the qualifiers, and eliminate matches - final int count = FolderConfiguration.getQualifierCount(); - for (int q = 0 ; q < count ; q++) { - // look to see if one resource has this qualifier. - // At the same time also record the best match value for the qualifier (if applicable). - - // The reference value, to find the best match. - // Note that this qualifier could be null. In which case any qualifier found in the - // possible match, will all be considered best match. - ResourceQualifier referenceQualifier = referenceConfig.getQualifier(q); - - boolean found = false; - ResourceQualifier bestMatch = null; // this is to store the best match. - for (Resource res : matchingResources) { - ResourceQualifier qualifier = res.getConfiguration().getQualifier(q); - if (qualifier != null) { - // set the flag. - found = true; - - // Now check for a best match. If the reference qualifier is null , - // any qualifier is a "best" match (we don't need to record all of them. - // Instead the non compatible ones are removed below) - if (referenceQualifier != null) { - if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) { - bestMatch = qualifier; - } - } - } - } - - // 4. If a resources has a qualifier at the current index, remove all the resources that - // do not have one, or whose qualifier value does not equal the best match found above - // unless there's no reference qualifier, in which case they are all considered - // "best" match. - if (found) { - for (int i = 0 ; i < matchingResources.size(); ) { - Resource res = matchingResources.get(i); - ResourceQualifier qualifier = res.getConfiguration().getQualifier(q); - - if (qualifier == null) { - // this resources has no qualifier of this type: rejected. - matchingResources.remove(res); - } else if (referenceQualifier != null && bestMatch != null && - bestMatch.equals(qualifier) == false) { - // there's a reference qualifier and there is a better match for it than - // this resource, so we reject it. - matchingResources.remove(res); - } else { - // looks like we keep this resource, move on to the next one. - i++; - } - } - - // at this point we may have run out of matching resources before going - // through all the qualifiers. - if (matchingResources.size() < 2) { - break; - } - } - } - - // Because we accept resources whose configuration have qualifiers where the reference - // configuration doesn't, we can end up with more than one match. In this case, we just - // take the first one. - if (matchingResources.size() == 0) { - return null; - } - return matchingResources.get(0); - } - - /** - * Checks if the list of {@link ResourceItem}s for the specified {@link ResourceType} needs - * to be updated. - * @param type the Resource Type. - */ - private void checkAndUpdate(ResourceType type) { - // get the list of folder that can output this type - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); - - for (ResourceFolderType folderType : folderTypes) { - List<ResourceFolder> folders = mFolderMap.get(folderType); - - if (folders != null) { - for (ResourceFolder folder : folders) { - if (folder.isTouched()) { - // if this folder is touched we need to update all the types that can - // be generated from a file in this folder. - // This will include 'type' obviously. - ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( - folderType); - for (ResourceType resType : resTypes) { - update(resType); - } - return; - } - } - } - } + @Override + protected ResourceItem createResourceItem(String name) { + return new ResourceItem(name); } /** - * Updates the list of {@link ResourceItem} objects associated with a {@link ResourceType}. - * This will reset the touch status of all the folders that can generate this resource type. - * @param type the Resource Type. + * Returns a dynamic integer for the given resource name, creating it if it doesn't + * already exist. + * + * @param name the name of the resource + * @return an integer. + * + * @see #resetDynamicIds() */ - private void update(ResourceType type) { - // get the cache list, and lets make a backup - List<ProjectResourceItem> items = mResourceMap.get(type); - List<ProjectResourceItem> backup = new ArrayList<ProjectResourceItem>(); - - if (items == null) { - items = new ArrayList<ProjectResourceItem>(); - mResourceMap.put(type, items); - } else { - // backup the list - backup.addAll(items); - - // we reset the list itself. - items.clear(); - } - - // get the list of folder that can output this type - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); - - for (ResourceFolderType folderType : folderTypes) { - List<ResourceFolder> folders = mFolderMap.get(folderType); - - if (folders != null) { - for (ResourceFolder folder : folders) { - items.addAll(folder.getResources(type, this)); - folder.resetTouch(); - } - } - } - - // now items contains the new list. We "merge" it with the backup list. - // Basically, we need to keep the old instances of ResourceItem (where applicable), - // but replace them by the content of the new items. - // This will let the resource explorer keep the expanded state of the nodes whose data - // is a ResourceItem object. - if (backup.size() > 0) { - // this is not going to change as we're only replacing instances. - int count = items.size(); - - for (int i = 0 ; i < count;) { - // get the "new" item - ProjectResourceItem item = items.get(i); - - // look for a similar item in the old list. - ProjectResourceItem foundOldItem = null; - for (ProjectResourceItem oldItem : backup) { - if (oldItem.getName().equals(item.getName())) { - foundOldItem = oldItem; - break; - } - } - - if (foundOldItem != null) { - // erase the data of the old item with the data from the new one. - foundOldItem.replaceWith(item); - - // remove the old and new item from their respective lists - items.remove(i); - backup.remove(foundOldItem); - - // add the old item to the new list - items.add(foundOldItem); - } else { - // this is a new item, we skip to the next object - i++; - } - } - } - - // if this is the ResourceType.ID, we create the actual list, from this list and - // the compiled resource list. - if (type == ResourceType.ID) { - mergeIdResources(); - } else { - // else this is the list that will actually be displayed, so we sort it. - Collections.sort(items); - } - } - private Integer getDynamicId(String name) { synchronized (mDynamicIds) { Integer value = mDynamicIds.get(name); if (value == null) { - value = new Integer(++mDynamicSeed); + value = Integer.valueOf(++mDynamicSeed); mDynamicIds.put(name, value); + mRevDynamicIds.put(value, name); } return value; @@ -898,30 +275,14 @@ public class ProjectResources implements IResourceRepository { } /** - * Looks up an existing {@link ProjectResourceItem} by {@link ResourceType} and name. - * @param type the Resource Type. - * @param name the Resource name. - * @return the existing ResourceItem or null if no match was found. - */ - protected ProjectResourceItem findResourceItem(ResourceType type, String name) { - List<ProjectResourceItem> list = mResourceMap.get(type); - - for (ProjectResourceItem item : list) { - if (name.equals(item.getName())) { - return item; - } - } - - return null; - } - - /** * Sets compiled resource information. + * * @param resIdValueToNameMap a map of compiled resource id to resource name. - * The map is acquired by the {@link ProjectResources} object. - * @param styleableValueMap + * The map is acquired by the {@link ProjectResources} object. + * @param styleableValueMap a map of (int[], name) for the styleable information. The map is + * acquired by the {@link ProjectResources} object. * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}. - * The list is acquired by the {@link ProjectResources} object. + * The list is acquired by the {@link ProjectResources} object. */ void setCompiledResources(Map<Integer, Pair<ResourceType, String>> resIdValueToNameMap, Map<IntArrayWrapper, String> styleableValueMap, @@ -932,77 +293,71 @@ public class ProjectResources implements IResourceRepository { mergeIdResources(); } + @Override + protected void postUpdate() { + super.postUpdate(); + mergeIdResources(); + } + /** * Merges the list of ID resource coming from R.java and the list of ID resources * coming from XML declaration into the cached list {@link #mIdResourceList}. */ void mergeIdResources() { - // get the list of IDs coming from XML declaration. Those ids are present in - // mCompiledIdResources already, so we'll need to use those instead of creating - // new IdResourceItem - List<ProjectResourceItem> xmlIdResources = mResourceMap.get(ResourceType.ID); - - synchronized (mIdResourceList) { - // copy the currently cached items. - ArrayList<IdResourceItem> oldItems = new ArrayList<IdResourceItem>(); - oldItems.addAll(mIdResourceList); - - // empty the current list - mIdResourceList.clear(); - - // get the list of compile id resources. - Map<String, Integer> idMap = null; - if (mResourceValueMap != null) { - idMap = mResourceValueMap.get(ResourceType.ID); - } + if (mResourceValueMap == null) { + return; + } - if (idMap == null) { - if (xmlIdResources != null) { - for (ProjectResourceItem resourceItem : xmlIdResources) { - // check the actual class just for safety. - if (resourceItem instanceof IdResourceItem) { - mIdResourceList.add((IdResourceItem)resourceItem); - } - } - } - } else { - // loop on the full list of id, and look for a match in the old list, - // in the list coming from XML (in case a new XML item was created.) - - Set<String> idSet = idMap.keySet(); - - idLoop: for (String idResource : idSet) { - // first look in the XML list in case an id went from inline to XML declared. - if (xmlIdResources != null) { - for (ProjectResourceItem resourceItem : xmlIdResources) { - if (resourceItem instanceof IdResourceItem && - resourceItem.getName().equals(idResource)) { - mIdResourceList.add((IdResourceItem)resourceItem); - continue idLoop; - } - } - } + // get the current ID values + List<ResourceItem> resources = mResourceMap.get(ResourceType.ID); + + // get the ID values coming from the R class. + Map<String, Integer> rResources = mResourceValueMap.get(ResourceType.ID); - // if we haven't found it, look in the old items. - int count = oldItems.size(); - for (int i = 0 ; i < count ; i++) { - IdResourceItem resourceItem = oldItems.get(i); - if (resourceItem.getName().equals(idResource)) { - oldItems.remove(i); - mIdResourceList.add(resourceItem); - continue idLoop; + if (rResources != null) { + Map<String, Integer> copy; + + if (resources == null) { + resources = new ArrayList<ResourceItem>(rResources.entrySet().size()); + mResourceMap.put(ResourceType.ID, resources); + copy = rResources; + } else { + // make a copy of the compiled Resources. + // As we loop on the full resources, we'll check with this copy map and remove + // from it all the resources we find in the full list. + // At the end, whatever is in the copy of the compile list is not in the full map, + // and should be added as inlined resource items. + copy = new HashMap<String, Integer>(rResources); + + for (int i = 0 ; i < resources.size(); ) { + ResourceItem item = resources.get(i); + String name = item.getName(); + if (item.isDeclaredInline()) { + // This ID is declared inline in the full resource map. + // Check if it's also in the compiled version, in which case we can keep it. + // Otherwise, if it doesn't exist in the compiled map, remove it from the + // full map. + // Since we're going to remove it from the copy map either way, we can use + // remove to test if it's there + if (copy.remove(name) != null) { + // there is a match in the compiled list, do nothing, keep current one. + i++; + } else { + // the ID is now gone, remove it from the list + resources.remove(i); } + } else { + // not an inline item, remove it from the copy. + copy.remove(name); + i++; } - - // if we haven't found it, it looks like it's a new id that was - // declared inline. - mIdResourceList.add(new IdResourceItem(idResource, - true /* isDeclaredInline */)); } } - // now we sort the list - Collections.sort(mIdResourceList); + // now add what's left in copy to the list + for (String name : copy.keySet()) { + resources.add(new InlineResourceItem(name)); + } } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/Resource.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/Resource.java deleted file mode 100644 index fd9005b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/Resource.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.resources.manager; - -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; - -/** - * Base class for file system resource items (Folders, Files). - */ -public abstract class Resource { - private boolean mTouched = true; - - /** - * Returns the {@link FolderConfiguration} for this object. - */ - public abstract FolderConfiguration getConfiguration(); - - /** - * Indicates that the underlying file was changed. - */ - public final void touch() { - mTouched = true; - } - - public final boolean isTouched() { - return mTouched; - } - - public final void resetTouch() { - mTouched = false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java index 684620b..e41cde5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java @@ -16,22 +16,28 @@ package com.android.ide.eclipse.adt.internal.resources.manager; +import com.android.annotations.VisibleForTesting; +import com.android.annotations.VisibleForTesting.Visibility; +import com.android.ide.common.resources.FrameworkResources; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFolderListener; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; +import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; import com.android.ide.eclipse.adt.io.IFileWrapper; import com.android.ide.eclipse.adt.io.IFolderWrapper; -import com.android.resources.ResourceType; +import com.android.io.FolderWrapper; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.IAbstractResource; +import com.android.resources.ResourceFolderType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.FolderWrapper; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.IAbstractResource; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -47,6 +53,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * The ResourceManager tracks resources for all opened projects. @@ -66,12 +73,10 @@ import java.util.HashMap; * @see ProjectResources */ public final class ResourceManager { + public final static boolean DEBUG = false; private final static ResourceManager sThis = new ResourceManager(); - /** List of the qualifier object helping for the parsing of folder names */ - private final ResourceQualifier[] mQualifiers; - /** * Map associating project resource with project objects. * <p/><b>All accesses must be inside a synchronized(mMap) block</b>, and do as a little as @@ -110,7 +115,9 @@ public final class ResourceManager { * @param monitor The global project monitor */ public static void setup(GlobalProjectMonitor monitor) { + monitor.addResourceEventListener(sThis.mResourceEventListener); monitor.addProjectListener(sThis.mProjectListener); + int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED; monitor.addFolderListener(sThis.mFolderListener, mask); monitor.addFileListener(sThis.mFileListener, mask); @@ -156,6 +163,40 @@ public final class ResourceManager { } } + private class ResourceEventListener implements IResourceEventListener { + private final List<IProject> mChangedProjects = new ArrayList<IProject>(); + + public void resourceChangeEventEnd() { + for (IProject project : mChangedProjects) { + ProjectResources resources; + synchronized (mMap) { + resources = mMap.get(project); + } + + resources.postUpdate(); + } + + mChangedProjects.clear(); + } + + public void resourceChangeEventStart() { + // pass + } + + void addProject(IProject project) { + if (mChangedProjects.contains(project) == false) { + mChangedProjects.add(project); + } + } + } + + /** + * Delegate listener for resource changes. This is called before and after any calls to the + * project and file listeners (for a given resource change event). + */ + private ResourceEventListener mResourceEventListener = new ResourceEventListener(); + + /** * Implementation of the {@link IFolderListener} as an internal class so that the methods * do not appear in the public API of {@link ResourceManager}. @@ -167,7 +208,7 @@ public final class ResourceManager { final IProject project = folder.getProject(); try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e) { @@ -175,6 +216,8 @@ public final class ResourceManager { return; } + mResourceEventListener.addProject(project); + switch (kind) { case IResourceDelta.ADDED: // checks if the folder is under res. @@ -194,8 +237,8 @@ public final class ResourceManager { } } - ResourceFolder newFolder = processFolder(new IFolderWrapper(folder), - resources); + ResourceFolder newFolder = resources.processFolder( + new IFolderWrapper(folder)); if (newFolder != null) { notifyListenerOnFolderChange(project, newFolder, kind); } @@ -203,13 +246,13 @@ public final class ResourceManager { } break; case IResourceDelta.CHANGED: + // only call the listeners. synchronized (mMap) { resources = mMap.get(folder.getProject()); } if (resources != null) { ResourceFolder resFolder = resources.getResourceFolder(folder); if (resFolder != null) { - resFolder.touch(); notifyListenerOnFolderChange(project, resFolder, kind); } } @@ -223,7 +266,8 @@ public final class ResourceManager { ResourceFolderType type = ResourceFolderType.getFolderType( folder.getName()); - ResourceFolder removedFolder = resources.removeFolder(type, folder); + ResourceFolder removedFolder = resources.removeFolder(type, + new IFolderWrapper(folder)); if (removedFolder != null) { notifyListenerOnFolderChange(project, removedFolder, kind); } @@ -251,12 +295,10 @@ public final class ResourceManager { * @see IFileListener#fileChanged */ public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - ProjectResources resources; - final IProject project = file.getProject(); try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e) { @@ -264,80 +306,38 @@ public final class ResourceManager { return; } - switch (kind) { - case IResourceDelta.ADDED: - // checks if the file is under res/something. - IPath path = file.getFullPath(); - - if (path.segmentCount() == 4) { - if (isInResFolder(path)) { - // get the project and its resources - synchronized (mMap) { - resources = mMap.get(project); - } - - IContainer container = file.getParent(); - if (container instanceof IFolder && resources != null) { - - ResourceFolder folder = resources.getResourceFolder( - (IFolder)container); - - if (folder != null) { - ResourceFile resFile = processFile( - new IFileWrapper(file), folder); - notifyListenerOnFileChange(project, resFile, kind); - } - } - } - } - break; - case IResourceDelta.CHANGED: - // try to find a matching ResourceFile - synchronized (mMap) { - resources = mMap.get(project); - } - if (resources != null) { - IContainer container = file.getParent(); - if (container instanceof IFolder) { - ResourceFolder resFolder = resources.getResourceFolder( - (IFolder)container); + // get the project resources + ProjectResources resources; + synchronized (mMap) { + resources = mMap.get(project); + } - // we get the delete on the folder before the file, so it is possible - // the associated ResourceFolder doesn't exist anymore. - if (resFolder != null) { - // get the resourceFile, and touch it. - ResourceFile resFile = resFolder.getFile(file); - if (resFile != null) { - resFile.touch(); - notifyListenerOnFileChange(project, resFile, kind); - } - } - } - } - break; - case IResourceDelta.REMOVED: - // try to find a matching ResourceFile - synchronized (mMap) { - resources = mMap.get(project); - } - if (resources != null) { - IContainer container = file.getParent(); - if (container instanceof IFolder) { - ResourceFolder resFolder = resources.getResourceFolder( - (IFolder)container); + if (resources == null) { + return; + } - // we get the delete on the folder before the file, so it is possible - // the associated ResourceFolder doesn't exist anymore. - if (resFolder != null) { - // remove the file - ResourceFile resFile = resFolder.removeFile(file); - if (resFile != null) { - notifyListenerOnFileChange(project, resFile, kind); - } - } + // checks if the file is under res/something. + IPath path = file.getFullPath(); + + if (path.segmentCount() == 4) { + if (isInResFolder(path)) { + IContainer container = file.getParent(); + if (container instanceof IFolder) { + + ResourceFolder folder = resources.getResourceFolder( + (IFolder)container); + + // folder can be null as when the whole folder is deleted, the + // REMOVED event for the folder comes first. In this case, the + // folder will have taken care of things. + if (folder != null) { + ResourceFile resFile = folder.processFile( + new IFileWrapper(file), + ResourceHelper.getResourceDeltaKind(kind)); + notifyListenerOnFileChange(project, resFile, kind); } } - break; + } } } }; @@ -409,15 +409,16 @@ public final class ResourceManager { * Loads and returns the resources for a given {@link IAndroidTarget} * @param androidTarget the target from which to load the framework resources */ - public ProjectResources loadFrameworkResources(IAndroidTarget androidTarget) { + public ResourceRepository loadFrameworkResources(IAndroidTarget androidTarget) { String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES); FolderWrapper frameworkRes = new FolderWrapper(osResourcesPath); if (frameworkRes.exists()) { - ProjectResources resources = new ProjectResources(); + FrameworkResources resources = new FrameworkResources(); try { loadResources(resources, frameworkRes); + resources.loadPublicResources(frameworkRes, AdtPlugin.getDefault()); return resources; } catch (IOException e) { // since we test that folders are folders, and files are files, this shouldn't @@ -429,7 +430,7 @@ public final class ResourceManager { } /** - * Loads the resources from a folder, and fills the given {@link ProjectResources}. + * Loads the resources from a folder, and fills the given {@link ResourceRepository}. * <p/> * This is mostly a utility method that should not be used to process actual Eclipse projects * (Those are loaded with {@link #createProject(IProject)} for new project or @@ -443,19 +444,20 @@ public final class ResourceManager { * setting rendering tests. * * - * @param resources The {@link ProjectResources} files to load. It is expected that the - * framework flag has been properly setup. This is filled up with the content of the folder. + * @param resources The {@link ResourceRepository} files to fill. + * This is filled up with the content of the folder. * @param rootFolder The folder to read the resources from. This is the top level * resource folder (res/) * @throws IOException */ - public void loadResources(ProjectResources resources, IAbstractFolder rootFolder) + @VisibleForTesting(visibility=Visibility.PRIVATE) + public void loadResources(ResourceRepository resources, IAbstractFolder rootFolder) throws IOException { IAbstractResource[] files = rootFolder.listMembers(); for (IAbstractResource file : files) { if (file instanceof IAbstractFolder) { IAbstractFolder folder = (IAbstractFolder) file; - ResourceFolder resFolder = processFolder(folder, resources); + ResourceFolder resFolder = resources.processFolder(folder); if (resFolder != null) { // now we process the content of the folder @@ -463,15 +465,13 @@ public final class ResourceManager { for (IAbstractResource childRes : children) { if (childRes instanceof IAbstractFile) { - processFile((IAbstractFile) childRes, resFolder); + resFolder.processFile((IAbstractFile) childRes, + ResourceHelper.getResourceDeltaKind(IResourceDelta.ADDED)); } } } } } - - // now that we have loaded the files, we need to force load the resources from them - resources.loadAll(); } /** @@ -481,7 +481,7 @@ public final class ResourceManager { private void createProject(IProject project) { if (project.isOpen()) { try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e1) { @@ -507,8 +507,8 @@ public final class ResourceManager { for (IResource res : resources) { if (res.getType() == IResource.FOLDER) { IFolder folder = (IFolder)res; - ResourceFolder resFolder = processFolder(new IFolderWrapper(folder), - projectResources); + ResourceFolder resFolder = projectResources.processFolder( + new IFolderWrapper(folder)); if (resFolder != null) { // now we process the content of the folder @@ -518,12 +518,16 @@ public final class ResourceManager { if (fileRes.getType() == IResource.FILE) { IFile file = (IFile)fileRes; - processFile(new IFileWrapper(file), resFolder); + resFolder.processFile(new IFileWrapper(file), + ResourceHelper.getResourceDeltaKind( + IResourceDelta.ADDED)); } } } } } + + projectResources.postUpdate(); } catch (CoreException e) { // This happens if the project is closed or if the folder doesn't exist. // Since we already test for that, we can ignore this exception. @@ -532,111 +536,6 @@ public final class ResourceManager { } } - /** - * Creates a {@link FolderConfiguration} matching the folder segments. - * @param folderSegments The segments of the folder name. The first segments should contain - * the name of the folder - * @return a FolderConfiguration object, or null if the folder name isn't valid.. - */ - public FolderConfiguration getConfig(String[] folderSegments) { - FolderConfiguration config = new FolderConfiguration(); - - // we are going to loop through the segments, and match them with the first - // available qualifier. If the segment doesn't match we try with the next qualifier. - // Because the order of the qualifier is fixed, we do not reset the first qualifier - // after each sucessful segment. - // If we run out of qualifier before processing all the segments, we fail. - - int qualifierIndex = 0; - int qualifierCount = mQualifiers.length; - - for (int i = 1 ; i < folderSegments.length; i++) { - String seg = folderSegments[i]; - if (seg.length() > 0) { - while (qualifierIndex < qualifierCount && - mQualifiers[qualifierIndex].checkAndSet(seg, config) == false) { - qualifierIndex++; - } - - // if we reached the end of the qualifier we didn't find a matching qualifier. - if (qualifierIndex == qualifierCount) { - return null; - } - - } else { - return null; - } - } - - return config; - } - - /** - * Processes a folder and adds it to the list of the project resources. - * @param folder the folder to process - * @param project the folder's project. - * @return the ConfiguredFolder created from this folder, or null if the process failed. - */ - private ResourceFolder processFolder(IAbstractFolder folder, ProjectResources project) { - // split the name of the folder in segments. - String[] folderSegments = folder.getName().split(FolderConfiguration.QUALIFIER_SEP); - - // get the enum for the resource type. - ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]); - - if (type != null) { - // get the folder configuration. - FolderConfiguration config = getConfig(folderSegments); - - if (config != null) { - ResourceFolder configuredFolder = project.add(type, config, folder); - - return configuredFolder; - } - } - - return null; - } - - /** - * Processes a file and adds it to its parent folder resource. - * @param file the underlying resource file. - * @param folder the parent of the resource file. - * @return the {@link ResourceFile} that was created. - */ - private ResourceFile processFile(IAbstractFile file, ResourceFolder folder) { - // get the type of the folder - ResourceFolderType type = folder.getType(); - - // look for this file if it's already been created - ResourceFile resFile = folder.getFile(file); - - if (resFile != null) { - // invalidate the file - resFile.touch(); - } else { - // create a ResourceFile for it. - - // check if that's a single or multi resource type folder. For now we define this by - // the number of possible resource type output by files in the folder. This does - // not make the difference between several resource types from a single file or - // the ability to have 2 files in the same folder generating 2 different types of - // resource. The former is handled by MultiResourceFile properly while we don't - // handle the latter. If we were to add this behavior we'd have to change this call. - ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(type); - - if (types.length == 1) { - resFile = new SingleResourceFile(file, folder); - } else { - resFile = new MultiResourceFile(file, folder); - } - - // add it to the folder - folder.addFile(resFile); - } - - return resFile; - } /** * Returns true if the path is under /project/res/ @@ -678,9 +577,19 @@ public final class ResourceManager { * Private constructor to enforce singleton design. */ private ResourceManager() { - // get the default qualifiers. - FolderConfiguration defaultConfig = new FolderConfiguration(); - defaultConfig.createDefault(); - mQualifiers = defaultConfig.getQualifiers(); + } + + // debug only + @SuppressWarnings("unused") + private String getKindString(int kind) { + if (DEBUG) { + switch (kind) { + case IResourceDelta.ADDED: return "ADDED"; + case IResourceDelta.REMOVED: return "REMOVED"; + case IResourceDelta.CHANGED: return "CHANGED"; + } + } + + return Integer.toString(kind); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java index 3bb125a..807acfc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.sdk; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; @@ -155,7 +155,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader // get the name of the entry. String entryPath = entry.getName(); - if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) { + if (!entryPath.endsWith(AdtConstants.DOT_CLASS)) { // only accept class files continue; } @@ -219,7 +219,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader while ((entry = zis.getNextEntry()) != null) { // get the name of the entry and convert to a class binary name String entryPath = entry.getName(); - if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) { + if (!entryPath.endsWith(AdtConstants.DOT_CLASS)) { // only accept class files continue; } @@ -341,7 +341,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader // The name is a binary name. Something like "android.R", or "android.R$id". // Make a path out of it. - String entryName = className.replaceAll("\\.", "/") + AndroidConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$ + String entryName = className.replaceAll("\\.", "/") + AdtConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$ // create streams to read the intermediary archive FileInputStream fis = new FileInputStream(mOsFrameworkLocation); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java index ae81ed6..69f1267 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetData.java @@ -18,15 +18,19 @@ package com.android.ide.eclipse.adt.internal.sdk; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimDescriptors; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors; +import com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors; import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableDescriptors; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; import com.android.ide.eclipse.adt.internal.editors.menu.descriptors.MenuDescriptors; import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; import com.android.ide.eclipse.adt.internal.editors.xml.descriptors.XmlDescriptors; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget.IOptionalLibrary; @@ -51,6 +55,10 @@ public class AndroidTargetData { public final static int DESCRIPTOR_SEARCHABLE = 6; public final static int DESCRIPTOR_PREFERENCES = 7; public final static int DESCRIPTOR_APPWIDGET_PROVIDER = 8; + public final static int DESCRIPTOR_DRAWABLE = 9; + public final static int DESCRIPTOR_ANIMATOR = 10; + public final static int DESCRIPTOR_ANIM = 11; + public final static int DESCRIPTOR_COLOR = 12; private final IAndroidTarget mTarget; @@ -66,16 +74,18 @@ public class AndroidTargetData { */ private Hashtable<String, String[]> mAttributeValues = new Hashtable<String, String[]>(); - private IResourceRepository mSystemResourceRepository; - private AndroidManifestDescriptors mManifestDescriptors; + private DrawableDescriptors mDrawableDescriptors; + private AnimatorDescriptors mAnimatorDescriptors; + private AnimDescriptors mAnimDescriptors; + private ColorDescriptors mColorDescriptors; private LayoutDescriptors mLayoutDescriptors; private MenuDescriptors mMenuDescriptors; private XmlDescriptors mXmlDescriptors; private Map<String, Map<String, Integer>> mEnumValueMap; - private ProjectResources mFrameworkResources; + private ResourceRepository mFrameworkResources; private LayoutLibrary mLayoutLibrary; private boolean mLayoutBridgeInit = false; @@ -86,14 +96,16 @@ public class AndroidTargetData { /** * Creates an AndroidTargetData object. - * @param platformLibraries - * @param optionalLibraries */ - void setExtraData(IResourceRepository systemResourceRepository, + void setExtraData( AndroidManifestDescriptors manifestDescriptors, LayoutDescriptors layoutDescriptors, MenuDescriptors menuDescriptors, XmlDescriptors xmlDescriptors, + DrawableDescriptors drawableDescriptors, + AnimatorDescriptors animatorDescriptors, + AnimDescriptors animDescriptors, + ColorDescriptors colorDescriptors, Map<String, Map<String, Integer>> enumValueMap, String[] permissionValues, String[] activityIntentActionValues, @@ -102,16 +114,19 @@ public class AndroidTargetData { String[] intentCategoryValues, String[] platformLibraries, IOptionalLibrary[] optionalLibraries, - ProjectResources resources, + ResourceRepository frameworkResources, LayoutLibrary layoutLibrary) { - mSystemResourceRepository = systemResourceRepository; mManifestDescriptors = manifestDescriptors; + mDrawableDescriptors = drawableDescriptors; + mAnimatorDescriptors = animatorDescriptors; + mAnimDescriptors = animDescriptors; + mColorDescriptors = colorDescriptors; mLayoutDescriptors = layoutDescriptors; mMenuDescriptors = menuDescriptors; mXmlDescriptors = xmlDescriptors; mEnumValueMap = enumValueMap; - mFrameworkResources = resources; + mFrameworkResources = frameworkResources; mLayoutLibrary = layoutLibrary; setPermissions(permissionValues); @@ -120,10 +135,6 @@ public class AndroidTargetData { setOptionalLibraries(platformLibraries, optionalLibraries); } - public IResourceRepository getSystemResources() { - return mSystemResourceRepository; - } - /** * Returns an {@link IDescriptorProvider} from a given Id. * The Id can be one of {@link #DESCRIPTOR_MANIFEST}, {@link #DESCRIPTOR_LAYOUT}, @@ -149,6 +160,14 @@ public class AndroidTargetData { return mXmlDescriptors.getAppWidgetProvider(); case DESCRIPTOR_SEARCHABLE: return mXmlDescriptors.getSearchableProvider(); + case DESCRIPTOR_DRAWABLE: + return mDrawableDescriptors; + case DESCRIPTOR_ANIMATOR: + return mAnimatorDescriptors; + case DESCRIPTOR_ANIM: + return mAnimDescriptors; + case DESCRIPTOR_COLOR: + return mColorDescriptors; default : throw new IllegalArgumentException(); } @@ -162,6 +181,34 @@ public class AndroidTargetData { } /** + * Returns the drawable descriptors + */ + public DrawableDescriptors getDrawableDescriptors() { + return mDrawableDescriptors; + } + + /** + * Returns the animation descriptors + */ + public AnimDescriptors getAnimDescriptors() { + return mAnimDescriptors; + } + + /** + * Returns the color descriptors + */ + public ColorDescriptors getColorDescriptors() { + return mColorDescriptors; + } + + /** + * Returns the animator descriptors + */ + public AnimatorDescriptors getAnimatorDescriptors() { + return mAnimatorDescriptors; + } + + /** * Returns the layout Descriptors. */ public LayoutDescriptors getLayoutDescriptors() { @@ -239,7 +286,7 @@ public class AndroidTargetData { /** * Returns the {@link ProjectResources} containing the Framework Resources. */ - public ProjectResources getFrameworkResources() { + public ResourceRepository getFrameworkResources() { return mFrameworkResources; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java index 6426fdb..19b2e3d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidTargetParser.java @@ -17,19 +17,20 @@ package com.android.ide.eclipse.adt.internal.sdk; import com.android.ide.common.rendering.LayoutLibrary; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.common.resources.platform.AttrsXmlParser; import com.android.ide.common.resources.platform.DeclareStyleableInfo; import com.android.ide.common.resources.platform.ViewClassInfo; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimDescriptors; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimatorDescriptors; +import com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors; +import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableDescriptors; import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors; import com.android.ide.eclipse.adt.internal.editors.manifest.descriptors.AndroidManifestDescriptors; import com.android.ide.eclipse.adt.internal.editors.menu.descriptors.MenuDescriptors; import com.android.ide.eclipse.adt.internal.editors.xml.descriptors.XmlDescriptors; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.resources.ResourceType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; @@ -86,7 +87,7 @@ public final class AndroidTargetParser { try { SubMonitor progress = SubMonitor.convert(monitor, String.format("Parsing SDK %1$s", mAndroidTarget.getName()), - 13); + 16); AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget); @@ -101,15 +102,6 @@ public final class AndroidTargetParser { return Status.CANCEL_STATUS; } - // get the resource Ids. - progress.subTask("Resource IDs"); - IResourceRepository frameworkRepository = collectResourceIds(classLoader); - progress.worked(1); - - if (progress.isCanceled()) { - return Status.CANCEL_STATUS; - } - // get the permissions progress.subTask("Permissions"); String[] permissionValues = collectPermissions(classLoader); @@ -230,9 +222,42 @@ public final class AndroidTargetParser { preferenceGroupsInfo); progress.worked(1); + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + DrawableDescriptors drawableDescriptors = new DrawableDescriptors(); + Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList(); + drawableDescriptors.updateDescriptors(map); + progress.worked(1); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + AnimatorDescriptors animatorDescriptors = new AnimatorDescriptors(); + animatorDescriptors.updateDescriptors(map); + progress.worked(1); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + AnimDescriptors animDescriptors = new AnimDescriptors(); + animDescriptors.updateDescriptors(map); + progress.worked(1); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + ColorDescriptors colorDescriptors = new ColorDescriptors(); + colorDescriptors.updateDescriptors(map); + progress.worked(1); + // load the framework resources. - ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources( - mAndroidTarget); + ResourceRepository frameworkResources = + ResourceManager.getInstance().loadFrameworkResources(mAndroidTarget); progress.worked(1); // now load the layout lib bridge @@ -243,11 +268,15 @@ public final class AndroidTargetParser { progress.worked(1); // and finally create the PlatformData with all that we loaded. - targetData.setExtraData(frameworkRepository, + targetData.setExtraData( manifestDescriptors, layoutDescriptors, menuDescriptors, xmlDescriptors, + drawableDescriptors, + animatorDescriptors, + animDescriptors, + colorDescriptors, enumValueMap, permissionValues, activity_actions.toArray(new String[activity_actions.size()]), @@ -256,7 +285,7 @@ public final class AndroidTargetParser { categories.toArray(new String[categories.size()]), mAndroidTarget.getPlatformLibraries(), mAndroidTarget.getOptionalLibraries(), - resources, + frameworkResources, layoutBridge); Sdk.getCurrent().setTargetData(mAndroidTarget, targetData); @@ -290,72 +319,6 @@ public final class AndroidTargetParser { } /** - * Creates an IResourceRepository for the framework resources. - * - * @param classLoader The framework SDK jar classloader - * @return a map of the resources, or null if it failed. - */ - private IResourceRepository collectResourceIds( - AndroidJarLoader classLoader) { - try { - Class<?> r = classLoader.loadClass(SdkConstants.CLASS_R); - - if (r != null) { - Map<ResourceType, List<ResourceItem>> map = parseRClass(r); - if (map != null) { - return new FrameworkResourceRepository(map); - } - } - } catch (ClassNotFoundException e) { - AdtPlugin.logAndPrintError(e, TAG, - "Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$ - SdkConstants.CLASS_R, - mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR)); - } - - return null; - } - - /** - * Parse the R class and build the resource map. - * - * @param rClass the Class object representing the Resources. - * @return a map of the resource or null - */ - private Map<ResourceType, List<ResourceItem>> parseRClass(Class<?> rClass) { - // get the sub classes. - Class<?>[] classes = rClass.getClasses(); - - if (classes.length > 0) { - HashMap<ResourceType, List<ResourceItem>> map = - new HashMap<ResourceType, List<ResourceItem>>(); - - // get the fields of each class. - for (int c = 0 ; c < classes.length ; c++) { - Class<?> subClass = classes[c]; - String name = subClass.getSimpleName(); - - // get the matching ResourceType - ResourceType type = ResourceType.getEnum(name); - if (type != null) { - List<ResourceItem> list = new ArrayList<ResourceItem>(); - map.put(type, list); - - Field[] fields = subClass.getFields(); - - for (Field f : fields) { - list.add(new ResourceItem(f.getName())); - } - } - } - - return map; - } - - return null; - } - - /** * Loads, collects and returns the list of default permissions from the framework. * * @param classLoader The framework SDK jar classloader diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/FrameworkResourceRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/FrameworkResourceRepository.java deleted file mode 100644 index 247a888..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/FrameworkResourceRepository.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 com.android.ide.eclipse.adt.internal.sdk; - -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.resources.ResourceType; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Implementation of the {@link IResourceRepository} interface to hold the system resource Ids - * parsed by {@link AndroidTargetParser}. - */ -final class FrameworkResourceRepository implements IResourceRepository { - - private Map<ResourceType, List<ResourceItem>> mResourcesMap; - - public FrameworkResourceRepository(Map<ResourceType, List<ResourceItem>> systemResourcesMap) { - mResourcesMap = systemResourcesMap; - } - - public ResourceType[] getAvailableResourceTypes() { - if (mResourcesMap != null) { - Set<ResourceType> types = mResourcesMap.keySet(); - - if (types != null) { - return types.toArray(new ResourceType[types.size()]); - } - } - - return null; - } - - public ResourceItem[] getResources(ResourceType type) { - if (mResourcesMap != null) { - List<ResourceItem> items = mResourcesMap.get(type); - - if (items != null) { - return items.toArray(new ResourceItem[items.size()]); - } - } - - return null; - } - - public boolean hasResources(ResourceType type) { - if (mResourcesMap != null) { - List<ResourceItem> items = mResourcesMap.get(type); - - return (items != null && items.size() > 0); - } - - return false; - } - - public boolean isSystemRepository() { - return true; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java index 4f7b7f2..60b528a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDevice.java @@ -16,19 +16,19 @@ package com.android.ide.eclipse.adt.internal.sdk; -import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenRatioQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +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 org.w3c.dom.Document; import org.w3c.dom.Element; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java index 12c58e8..3ad4e61 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceHandler.java @@ -16,19 +16,19 @@ package com.android.ide.eclipse.adt.internal.sdk; -import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenRatioQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +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.resources.Density; import com.android.resources.Keyboard; import com.android.resources.KeyboardState; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java index 60c8d95..57d1e50 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/LayoutDeviceManager.java @@ -16,8 +16,8 @@ package com.android.ide.eclipse.adt.internal.sdk; +import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.adt.internal.sdk.LayoutDevice.DeviceConfig; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java index 5d925db..8d3ee7b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java @@ -20,7 +20,7 @@ import com.android.ddmlib.IDevice; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.build.DexWrapper; import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; @@ -31,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonit import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryDifference; import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; +import com.android.io.StreamException; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; @@ -41,7 +42,6 @@ import com.android.sdklib.internal.avd.AvdManager; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; import com.android.sdklib.internal.project.ProjectProperties.PropertyType; -import com.android.sdklib.io.StreamException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; @@ -743,7 +743,7 @@ public final class Sdk { private void onProjectRemoved(IProject project, boolean deleted) { try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e) { @@ -830,7 +830,7 @@ public final class Sdk { private void onProjectOpened(final IProject openedProject) { try { - if (openedProject.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (openedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e) { @@ -897,7 +897,7 @@ public final class Sdk { public void projectRenamed(IProject project, IPath from) { try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } } catch (CoreException e) { @@ -964,7 +964,7 @@ public final class Sdk { // the target. IProject iProject = file.getProject(); - if (iProject.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) { return; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sourcelookup/AdtSourceLookupDirector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sourcelookup/AdtSourceLookupDirector.java index 612ef76..e0e236b 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sourcelookup/AdtSourceLookupDirector.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sourcelookup/AdtSourceLookupDirector.java @@ -17,7 +17,9 @@ package com.android.ide.eclipse.adt.internal.sourcelookup;
-import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
@@ -32,6 +34,7 @@ import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.launching.JavaSourceLookupDirector;
+import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import java.io.File;
@@ -41,20 +44,35 @@ public class AdtSourceLookupDirector extends JavaSourceLookupDirector { public void initializeDefaults(ILaunchConfiguration configuration) throws CoreException {
dispose();
setLaunchConfiguration(configuration);
- String projectName = configuration.getAttribute("org.eclipse.jdt.launching.PROJECT_ATTR", //$NON-NLS-1$
+ String projectName =
+ configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
""); //$NON-NLS-1$
if (projectName != null && projectName.length() > 0) {
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (project != null && project.isOpen()) {
+ ProjectState state = Sdk.getProjectState(project);
+ if (state == null) {
+ initDefaults();
+ return;
+ }
+ IAndroidTarget target = state.getTarget();
+ if (target == null) {
+ initDefaults();
+ return;
+ }
+ String path = target.getPath(IAndroidTarget.ANDROID_JAR);
+ if (path == null) {
+ initDefaults();
+ return;
+ }
IJavaProject javaProject = JavaCore.create(project);
if (javaProject != null && javaProject.isOpen()) {
- IClasspathEntry[] entries = javaProject.getRawClasspath();
+ IClasspathEntry[] entries = javaProject.getResolvedClasspath(true);
IClasspathEntry androidEntry = null;
for (int i = 0; i < entries.length; i++) {
IClasspathEntry entry = entries[i];
- if (entry.getPath() != null
- && AndroidClasspathContainerInitializer.CONTAINER_ID.equals(entry
- .getPath().toString())) {
+ if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY
+ && path.equals(entry.getPath().toString())) {
androidEntry = entry;
break;
}
@@ -88,6 +106,10 @@ public class AdtSourceLookupDirector extends JavaSourceLookupDirector { }
}
}
+ initDefaults();
+ }
+
+ private void initDefaults() {
setSourceContainers(new ISourceContainer[] {
new DefaultSourceContainer()
});
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java index e8ca524..74120fd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java @@ -16,26 +16,27 @@ package com.android.ide.eclipse.adt.internal.ui; -import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.DockModeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NightModeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenRatioQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.VersionQualifier; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.AndroidConstants; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.DockModeQualifier; +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.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NavigationStateQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.ide.common.resources.configuration.ResourceQualifier; +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.VersionQualifier; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; import com.android.resources.Density; import com.android.resources.DockMode; import com.android.resources.Keyboard; @@ -490,7 +491,7 @@ public class ConfigurationSelector extends Composite { * @return true if success, or false if the folder name is not a valid name. */ public boolean setConfiguration(String[] folderSegments) { - FolderConfiguration config = ResourceManager.getInstance().getConfig(folderSegments); + FolderConfiguration config = FolderConfiguration.getConfig(folderSegments); if (config == null) { return false; @@ -509,7 +510,7 @@ public class ConfigurationSelector extends Composite { */ public boolean setConfiguration(String folderName) { // split the name of the folder in segments. - String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP); + String[] folderSegments = folderName.split(AndroidConstants.RES_QUALIFIER_SEP); return setConfiguration(folderSegments); } @@ -657,7 +658,7 @@ public class ConfigurationSelector extends Composite { public Image getColumnImage(Object element, int columnIndex) { // only one column, so we can ignore columnIndex if (element instanceof ResourceQualifier) { - return ((ResourceQualifier)element).getIcon(); + return ResourceHelper.getIcon(((ResourceQualifier)element).getClass()); } return null; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java index 1a9a78f..1d4fc13 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java @@ -15,7 +15,8 @@ */ package com.android.ide.eclipse.adt.internal.ui; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.resources.ResourceType; @@ -196,11 +197,11 @@ public class MarginChooser extends SelectionStatusDialog implements Listener { Button button = (Button) event.widget; // Open a resource chooser dialog for specified resource type. - IResourceRepository projectRepository = ResourceManager.getInstance() + ProjectResources projectRepository = ResourceManager.getInstance() .getProjectResources(mProject); - IResourceRepository systemRepository = mTargetData.getSystemResources(); + ResourceRepository frameworkRepository = mTargetData.getFrameworkResources(); ResourceChooser dlg = new ResourceChooser(mProject, ResourceType.DIMEN, - projectRepository, systemRepository, getShell()); + projectRepository, frameworkRepository, getShell()); Text text = (Text) button.getData(PROP_TEXTFIELD); dlg.setCurrentResource(text.getText().trim()); if (dlg.open() == Window.OK) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java index 2a170a4..b436471 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java @@ -16,12 +16,11 @@ package com.android.ide.eclipse.adt.internal.ui; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; -import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; import com.android.resources.ResourceType; import org.eclipse.core.resources.IProject; @@ -52,6 +51,7 @@ import org.eclipse.ui.dialogs.FilteredTree; import org.eclipse.ui.dialogs.PatternFilter; import org.eclipse.ui.dialogs.SelectionStatusDialog; +import java.util.Collection; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -66,7 +66,7 @@ public class ReferenceChooserDialog extends SelectionStatusDialog { private static IDialogSettings sDialogSettings = new DialogSettings(""); - private IResourceRepository mResources; + private ResourceRepository mProjectResources; private String mCurrentResource; private FilteredTree mFilteredTree; private Button mNewResButton; @@ -77,10 +77,11 @@ public class ReferenceChooserDialog extends SelectionStatusDialog { * @param project * @param parent */ - public ReferenceChooserDialog(IProject project, IResourceRepository resources, Shell parent) { + public ReferenceChooserDialog(IProject project, ResourceRepository projectResources, + Shell parent) { super(parent); mProject = project; - mResources = resources; + mProjectResources = projectResources; int shellStyle = getShellStyle(); setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE); @@ -113,8 +114,7 @@ public class ReferenceChooserDialog extends SelectionStatusDialog { ResourceType resourceType = (ResourceType)treeSelection.getFirstSegment(); ResourceItem resourceItem = (ResourceItem)treeSelection.getLastSegment(); - mCurrentResource = ResourceHelper.getXmlString(resourceType, - resourceItem, false /* system */); + mCurrentResource = resourceItem.getXmlString(resourceType, false /* system */); } } } @@ -177,7 +177,7 @@ public class ReferenceChooserDialog extends SelectionStatusDialog { mTreeViewer.setLabelProvider(new ResourceLabelProvider()); mTreeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */)); - mTreeViewer.setInput(mResources); + mTreeViewer.setInput(mProjectResources); } protected void handleSelection() { @@ -339,7 +339,8 @@ public class ReferenceChooserDialog extends SelectionStatusDialog { */ private void setupInitialSelection(ResourceType resourceType, String resourceName) { // get all the resources of this type - ResourceItem[] resourceItems = mResources.getResources(resourceType); + Collection<ResourceItem> resourceItems = + mProjectResources.getResourceItemsOfType(resourceType); for (ResourceItem resourceItem : resourceItems) { if (resourceName.equals(resourceItem.getName())) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java index 1a6a9de..5388b4e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java @@ -16,31 +16,24 @@ package com.android.ide.eclipse.adt.internal.ui; -import static com.android.ide.eclipse.adt.AndroidConstants.EXT_XML; -import static com.android.ide.eclipse.adt.AndroidConstants.WS_SEP; -import static com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors.NAME_ATTR; -import static com.android.sdklib.SdkConstants.FD_RESOURCES; -import static com.android.sdklib.SdkConstants.FD_VALUES; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; -import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors; -import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring; import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; import com.android.resources.ResourceType; +import com.android.util.Pair; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.text.IRegion; import org.eclipse.jface.window.Window; import org.eclipse.ltk.ui.refactoring.RefactoringWizard; import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation; @@ -60,77 +53,81 @@ import org.eclipse.ui.IWorkbench; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; import org.eclipse.ui.dialogs.SelectionStatusDialog; -import org.eclipse.wst.sse.core.StructuredModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A dialog to let the user select a resource based on a resource type. */ -@SuppressWarnings("restriction") // XML model public class ResourceChooser extends AbstractElementListSelectionDialog { + /** The return code from the dialog for the user choosing "Clear" */ + public static final int CLEAR_RETURN_CODE = -5; + /** The dialog button ID for the user choosing "Clear" */ + private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE; private Pattern mProjectResourcePattern; - private ResourceType mResourceType; - - private IResourceRepository mProjectResources; - - private final static boolean SHOW_SYSTEM_RESOURCE = false; // TODO re-enable at some point + private final ResourceRepository mProjectResources; + private final ResourceRepository mFrameworkResources; private Pattern mSystemResourcePattern; - private IResourceRepository mSystemResources; private Button mProjectButton; private Button mSystemButton; - + private Button mNewButton; private String mCurrentResource; - private final IProject mProject; + private IInputValidator mInputValidator; /** * Creates a Resource Chooser dialog. * @param project Project being worked on * @param type The type of the resource to choose * @param projectResources The repository for the project - * @param systemResources The System resource repository + * @param frameworkResources The Framework resource repository * @param parent the parent shell */ public ResourceChooser(IProject project, ResourceType type, - IResourceRepository projectResources, - IResourceRepository systemResources, + ResourceRepository projectResources, + ResourceRepository frameworkResources, Shell parent) { super(parent, new ResourceLabelProvider()); mProject = project; mResourceType = type; mProjectResources = projectResources; + mFrameworkResources = frameworkResources; mProjectResourcePattern = Pattern.compile( "@" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ - if (SHOW_SYSTEM_RESOURCE) { - mSystemResources = systemResources; - mSystemResourcePattern = Pattern.compile( - "@android:" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ - } + mSystemResourcePattern = Pattern.compile( + "@android:" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ setTitle("Resource Chooser"); setMessage(String.format("Choose a %1$s resource", mResourceType.getDisplayName().toLowerCase())); } + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/); + super.createButtonsForButtonBar(parent); + } + + @Override + protected void buttonPressed(int buttonId) { + super.buttonPressed(buttonId); + + if (buttonId == CLEAR_BUTTON_ID) { + assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL; + setReturnCode(CLEAR_RETURN_CODE); + close(); + } + } + public void setCurrentResource(String resource) { mCurrentResource = resource; } @@ -139,14 +136,21 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { return mCurrentResource; } + public void setInputValidator(IInputValidator inputValidator) { + mInputValidator = inputValidator; + } + @Override protected void computeResult() { Object[] elements = getSelectedElements(); if (elements.length == 1 && elements[0] instanceof ResourceItem) { ResourceItem item = (ResourceItem)elements[0]; - mCurrentResource = ResourceHelper.getXmlString(mResourceType, item, - SHOW_SYSTEM_RESOURCE && mSystemButton.getSelection()); + mCurrentResource = item.getXmlString(mResourceType, mSystemButton.getSelection()); + + if (mInputValidator != null && mInputValidator.isValid(mCurrentResource) != null) { + mCurrentResource = null; + } } } @@ -174,9 +178,6 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { * @param top the parent composite */ private void createButtons(Composite top) { - if (!SHOW_SYSTEM_RESOURCE) { - return; - } mProjectButton = new Button(top, SWT.RADIO); mProjectButton.setText("Project Resources"); mProjectButton.addSelectionListener(new SelectionAdapter() { @@ -184,7 +185,8 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); if (mProjectButton.getSelection()) { - setListElements(mProjectResources.getResources(mResourceType)); + setupResourceList(); + mNewButton.setEnabled(true); } } }); @@ -194,8 +196,9 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); - if (mProjectButton.getSelection()) { - setListElements(mSystemResources.getResources(mResourceType)); + if (mSystemButton.getSelection()) { + setupResourceList(); + mNewButton.setEnabled(false); } } }); @@ -206,16 +209,15 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { * @param top the parent composite */ private void createNewResButtons(Composite top) { - - Button newResButton = new Button(top, SWT.NONE); + mNewButton = new Button(top, SWT.NONE); String title = String.format("New %1$s...", mResourceType.getDisplayName()); - newResButton.setText(title); + mNewButton.setText(title); - // We only support adding new strings right now - newResButton.setEnabled(Hyperlinks.isValueResource(mResourceType)); + // We only support adding new values right now + mNewButton.setEnabled(ResourceHelper.isValueBasedResourceType(mResourceType)); - newResButton.addSelectionListener(new SelectionAdapter() { + mNewButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); @@ -223,18 +225,38 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { if (mResourceType == ResourceType.STRING) { createNewString(); } else { - assert Hyperlinks.isValueResource(mResourceType); + assert ResourceHelper.isValueBasedResourceType(mResourceType); String newName = createNewValue(mResourceType); if (newName != null) { // Recompute the "current resource" to select the new id - setupResourceList(); - selectItemName(newName); + ResourceItem[] items = setupResourceList(); + selectItemName(newName, items); } } } }); } + @Override + protected void handleSelectionChanged() { + super.handleSelectionChanged(); + if (mInputValidator != null) { + Object[] elements = getSelectedElements(); + if (elements.length == 1 && elements[0] instanceof ResourceItem) { + ResourceItem item = (ResourceItem)elements[0]; + String current = item.getXmlString(mResourceType, mSystemButton.getSelection()); + String error = mInputValidator.isValid(current); + IStatus status; + if (error != null) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); + } else { + status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); + } + updateStatus(status); + } + } + } + private String createNewValue(ResourceType type) { // Show a name/value dialog entering the key name and the value Shell shell = AdtPlugin.getDisplay().getActiveShell(); @@ -252,101 +274,11 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { return null; } - // Find "dimens.xml" file in res/values/ (or corresponding name for other - // value types) - String fileName = type.getName() + 's'; - String projectPath = FD_RESOURCES + WS_SEP + FD_VALUES + WS_SEP + fileName + '.' + EXT_XML; - IResource member = mProject.findMember(projectPath); - if (member != null) { - if (member instanceof IFile) { - IFile file = (IFile) member; - // File exists: Must add item to the XML - IModelManager manager = StructuredModelManager.getModelManager(); - IStructuredModel model = null; - try { - model = manager.getExistingModelForEdit(file); - if (model == null) { - model = manager.getModelForEdit(file); - } - if (model instanceof IDOMModel) { - model.beginRecording(this, String.format("Add %1$s", - type.getDisplayName())); - IDOMModel domModel = (IDOMModel) model; - Document document = domModel.getDocument(); - Element root = document.getDocumentElement(); - IStructuredDocument structuredDocument = model.getStructuredDocument(); - Node lastElement = null; - NodeList childNodes = root.getChildNodes(); - String indent = null; - for (int i = childNodes.getLength() - 1; i >= 0; i--) { - Node node = childNodes.item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - lastElement = node; - indent = AndroidXmlEditor.getIndent(structuredDocument, node); - break; - } - } - if (indent == null || indent.length() == 0) { - indent = " "; //$NON-NLS-1$ - } - Node nextChild = lastElement != null ? lastElement.getNextSibling() : null; - Text indentNode = document.createTextNode('\n' + indent); - root.insertBefore(indentNode, nextChild); - Element element = document.createElement(Hyperlinks.getTagName(type)); - element.setAttribute(NAME_ATTR, name); - root.insertBefore(element, nextChild); - Text valueNode = document.createTextNode(value); - element.appendChild(valueNode); - model.save(); - return name; - } - } catch (Exception e) { - AdtPlugin.log(e, "Cannot access XML value model"); - } finally { - if (model != null) { - model.endRecording(this); - model.releaseFromEdit(); - } - } - } - - return null; - } else { - // No such file exists: just create it - String prolog = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ - StringBuilder sb = new StringBuilder(prolog); - - String root = ResourcesDescriptors.ROOT_ELEMENT; - sb.append('<').append(root).append('>').append('\n'); - sb.append(" "); //$NON-NLS-1$ - sb.append('<'); - sb.append(type.getName()); - sb.append(" name=\""); //$NON-NLS-1$ - sb.append(name); - sb.append('"'); - sb.append('>'); - sb.append(value); - sb.append('<').append('/'); - sb.append(type.getName()); - sb.append(">\n"); //$NON-NLS-1$ - sb.append('<').append('/').append(root).append('>').append('\n'); - String result = sb.toString(); - String error = null; - try { - byte[] buf = result.getBytes("UTF8"); //$NON-NLS-1$ - InputStream stream = new ByteArrayInputStream(buf); - IFile file = mProject.getFile(new Path(projectPath)); - file.create(stream, true /*force*/, null /*progress*/); - return name; - } catch (UnsupportedEncodingException e) { - error = e.getMessage(); - } catch (CoreException e) { - error = e.getMessage(); - } - - error = String.format("Failed to generate %1$s: %2$s", name, error); - AdtPlugin.displayError("New Android XML File", error); + Pair<IFile, IRegion> resource = ResourceHelper.createResource(mProject, type, name, value); + if (resource != null) { + return name; } + return null; } @@ -361,10 +293,10 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { IDialogConstants.OK_ID) { // Recompute the "current resource" to select the new id - setupResourceList(); + ResourceItem[] items = setupResourceList(); // select it if possible - selectItemName(ref.getXmlStringId()); + selectItemName(ref.getXmlStringId(), items); } } catch (InterruptedException ex) { // Interrupted. Pass. @@ -372,37 +304,38 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { } /** - * @return The repository currently selected. + * Setups the current list. */ - private IResourceRepository getCurrentRepository() { - IResourceRepository repo = mProjectResources; + private ResourceItem[] setupResourceList() { + Collection<ResourceItem> items = null; + if (mProjectButton.getSelection()) { + items = mProjectResources.getResourceItemsOfType(mResourceType); + } else if (mSystemButton.getSelection()) { + items = mFrameworkResources.getResourceItemsOfType(mResourceType); + } - if (SHOW_SYSTEM_RESOURCE && mSystemButton.getSelection()) { - repo = mSystemResources; + if (items == null) { + items = Collections.emptyList(); } - return repo; - } - /** - * Setups the current list. - */ - private void setupResourceList() { - IResourceRepository repo = getCurrentRepository(); - setListElements(repo.getResources(mResourceType)); + ResourceItem[] arrayItems = items.toArray(new ResourceItem[items.size()]); + + // sort the array + Arrays.sort(arrayItems); + + setListElements(arrayItems); + + return arrayItems; } /** * Select an item by its name, if possible. */ - private void selectItemName(String itemName) { - if (itemName == null) { + private void selectItemName(String itemName, ResourceItem[] items) { + if (itemName == null || items == null) { return; } - IResourceRepository repo = getCurrentRepository(); - - ResourceItem[] items = repo.getResources(mResourceType); - for (ResourceItem item : items) { if (itemName.equals(item.getName())) { setSelection(new Object[] { item }); @@ -419,46 +352,46 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { boolean isSystem = false; String itemName = null; - // Is this a system resource? - // If not a system resource or if they are not available, this will be a project res. - if (SHOW_SYSTEM_RESOURCE) { + if (resourceString != null) { + // Is this a system resource? + // If not a system resource or if they are not available, this will be a project res. Matcher m = mSystemResourcePattern.matcher(resourceString); if (m.matches()) { itemName = m.group(1); isSystem = true; } - } - if (!isSystem && itemName == null) { - // Try to match project resource name - Matcher m = mProjectResourcePattern.matcher(resourceString); - if (m.matches()) { - itemName = m.group(1); + if (!isSystem && itemName == null) { + // Try to match project resource name + m = mProjectResourcePattern.matcher(resourceString); + if (m.matches()) { + itemName = m.group(1); + } } } // Update the repository selection - if (SHOW_SYSTEM_RESOURCE) { - mProjectButton.setSelection(!isSystem); - mSystemButton.setSelection(isSystem); - } + mProjectButton.setSelection(!isSystem); + mSystemButton.setSelection(isSystem); + mNewButton.setEnabled(!isSystem); // Update the list - setupResourceList(); + ResourceItem[] items = setupResourceList(); // If we have a selection name, select it if (itemName != null) { - selectItemName(itemName); + selectItemName(itemName, items); } } /** Dialog asking for a Name/Value pair */ - private static class NameValueDialog extends SelectionStatusDialog implements Listener { + private class NameValueDialog extends SelectionStatusDialog implements Listener { private org.eclipse.swt.widgets.Text mNameText; private org.eclipse.swt.widgets.Text mValueText; private String mInitialName; private String mName; private String mValue; + private ResourceNameValidator mValidator; public NameValueDialog(Shell parent, String initialName) { super(parent); @@ -471,7 +404,7 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { container.setLayout(new GridLayout(2, false)); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); // Wide enough to accommodate the error label - gridData.widthHint = 400; + gridData.widthHint = 500; container.setLayoutData(gridData); @@ -527,7 +460,15 @@ public class ResourceChooser extends AbstractElementListSelectionDialog { } else if (mValue.length() == 0) { status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "Enter a value"); } else { - status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); + if (mValidator == null) { + mValidator = ResourceNameValidator.create(false, mProject, mResourceType); + } + String error = mValidator.isValid(mName); + if (error != null) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, error); + } else { + status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, null); + } } updateStatus(status); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java index f57b74e..6eaa756 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceContentProvider.java @@ -16,15 +16,18 @@ package com.android.ide.eclipse.adt.internal.ui; -import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.ide.eclipse.adt.internal.resources.manager.ConfigurableResourceItem; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; import com.android.resources.ResourceType; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.Viewer; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * Content provider for the Resource Explorer TreeView. * Each level of the tree is represented by a different class. @@ -40,10 +43,10 @@ import org.eclipse.jface.viewers.Viewer; * <li>{@link ResourceFile}. (optional) This represents a particular version of the * {@link ResourceItem}. It is displayed as a list of resource qualifier. * </li> - * </ul> - * </ul> - * </ul> - * + * </ul> + * </ul> + * </ul> + * * @see ResourceLabelProvider */ public class ResourceContentProvider implements ITreeContentProvider { @@ -51,10 +54,10 @@ public class ResourceContentProvider implements ITreeContentProvider { /** * The current ProjectResources being displayed. */ - private IResourceRepository mResources; - + private ResourceRepository mResources; + private boolean mFullLevels; - + /** * Constructs a new content providers for resource display. * @param fullLevels if <code>true</code> the content provider will suppport all 3 levels. If @@ -66,9 +69,12 @@ public class ResourceContentProvider implements ITreeContentProvider { public Object[] getChildren(Object parentElement) { if (parentElement instanceof ResourceType) { - return mResources.getResources((ResourceType)parentElement); - } else if (mFullLevels && parentElement instanceof ConfigurableResourceItem) { - return ((ConfigurableResourceItem)parentElement).getSourceFileArray(); + Object[] array = mResources.getResourceItemsOfType( + (ResourceType)parentElement).toArray(); + Arrays.sort(array); + return array; + } else if (mFullLevels && parentElement instanceof ResourceItem) { + return ((ResourceItem)parentElement).getSourceFileArray(); } return null; } @@ -80,18 +86,20 @@ public class ResourceContentProvider implements ITreeContentProvider { public boolean hasChildren(Object element) { if (element instanceof ResourceType) { - return mResources.hasResources((ResourceType)element); - } else if (mFullLevels && element instanceof ConfigurableResourceItem) { - return ((ConfigurableResourceItem)element).hasAlternates(); + return mResources.hasResourcesOfType((ResourceType)element); + } else if (mFullLevels && element instanceof ResourceItem) { + return ((ResourceItem)element).hasAlternates(); } return false; } public Object[] getElements(Object inputElement) { - if (inputElement instanceof IResourceRepository) { - if ((IResourceRepository)inputElement == mResources) { + if (inputElement instanceof ResourceRepository) { + if ((ResourceRepository)inputElement == mResources) { // get the top level resources. - return mResources.getAvailableResourceTypes(); + List<ResourceType> types = mResources.getAvailableResourceTypes(); + Collections.sort(types); + return types.toArray(); } } @@ -103,8 +111,8 @@ public class ResourceContentProvider implements ITreeContentProvider { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - if (newInput instanceof IResourceRepository) { - mResources = (IResourceRepository)newInput; + if (newInput instanceof ResourceRepository) { + mResources = (ResourceRepository)newInput; } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java index f1810a1..df8a97a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceExplorerView.java @@ -16,15 +16,15 @@ package com.android.ide.eclipse.adt.internal.ui; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceItem; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; -import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResourceItem; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IResourceEventListener; import com.android.ide.eclipse.adt.io.IFileWrapper; -import com.android.sdklib.io.IAbstractFile; +import com.android.io.IAbstractFile; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -76,9 +76,9 @@ public class ResourceExplorerView extends ViewPart implements ISelectionListener // Note: keep using the obsolete AndroidConstants.EDITORS_NAMESPACE (which used // to be the Editors Plugin ID) to keep existing preferences functional. private final static String PREFS_COLUMN_RES = - AndroidConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col1"; //$NON-NLS-1$ + AdtConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col1"; //$NON-NLS-1$ private final static String PREFS_COLUMN_2 = - AndroidConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col2"; //$NON-NLS-1$ + AdtConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col2"; //$NON-NLS-1$ private Tree mTree; private TreeViewer mTreeViewer; @@ -138,10 +138,10 @@ public class ResourceExplorerView extends ViewPart implements ISelectionListener } } catch (PartInitException e) { } - } else if (element instanceof ProjectResourceItem) { + } else if (element instanceof ResourceItem) { // if it's a ResourceItem, we open the first file, but only if // there's no alternate files. - ProjectResourceItem item = (ProjectResourceItem)element; + ResourceItem item = (ResourceItem)element; if (item.isEditableDirectly()) { ResourceFile[] files = item.getSourceFileArray(); @@ -240,7 +240,7 @@ public class ResourceExplorerView extends ViewPart implements ISelectionListener try { // if it's an android project, then we get its resources, and feed them // to the tree viewer. - if (project.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT)) { if (mCurrentProject != project) { ProjectResources projRes = ResourceManager.getInstance().getProjectResources( project); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java index 50e1d07..4453acb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceLabelProvider.java @@ -16,11 +16,8 @@ package com.android.ide.eclipse.adt.internal.ui; -import com.android.ide.eclipse.adt.internal.resources.IIdResourceItem; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.ide.eclipse.adt.internal.resources.manager.ConfigurableResourceItem; -import com.android.ide.eclipse.adt.internal.resources.manager.IdResourceItem; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceItem; import com.android.resources.ResourceType; import org.eclipse.jface.viewers.ILabelProvider; @@ -47,15 +44,15 @@ import org.eclipse.ui.PlatformUI; * <li>{@link ResourceFile}. This represents a particular version of the {@link ResourceItem}. * It is displayed as a list of resource qualifier. * </li> - * </ul> - * </ul> - * </ul> - * + * </ul> + * </ul> + * </ul> + * * @see ResourceContentProvider */ public class ResourceLabelProvider implements ILabelProvider, ITableLabelProvider { private Image mWarningImage; - + public ResourceLabelProvider() { mWarningImage = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( ISharedImages.IMG_OBJS_WARN_TSK).createImage(); @@ -94,8 +91,8 @@ public class ResourceLabelProvider implements ILabelProvider, ITableLabelProvide public Image getColumnImage(Object element, int columnIndex) { if (columnIndex == 1) { - if (element instanceof ConfigurableResourceItem) { - ConfigurableResourceItem item = (ConfigurableResourceItem)element; + if (element instanceof ResourceItem) { + ResourceItem item = (ResourceItem)element; if (item.hasDefault() == false) { return mWarningImage; } @@ -116,19 +113,18 @@ public class ResourceLabelProvider implements ILabelProvider, ITableLabelProvide } break; case 1: - if (element instanceof ConfigurableResourceItem) { - ConfigurableResourceItem item = (ConfigurableResourceItem)element; - int count = item.getAlternateCount(); - if (count > 0) { - if (item.hasDefault()) { - count++; - } - return String.format("%1$d version(s)", count); - } - } else if (element instanceof IIdResourceItem) { - IIdResourceItem idResource = (IIdResourceItem)element; - if (idResource.isDeclaredInline()) { + if (element instanceof ResourceItem) { + ResourceItem item = (ResourceItem)element; + if (item.isDeclaredInline()) { return "Declared inline"; + } else { + int count = item.getAlternateCount(); + if (count > 0) { + if (item.hasDefault()) { + count++; + } + return String.format("%1$d version(s)", count); + } } } return null; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java index 4a2a77a..c1ce672 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java @@ -59,7 +59,7 @@ import java.util.List; */ public final class ExportWizard extends Wizard implements IExportWizard { - private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$ + private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$ private static final String PAGE_PROJECT_CHECK = "Page_ProjectCheck"; //$NON-NLS-1$ private static final String PAGE_KEYSTORE_SELECTION = "Page_KeystoreSelection"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java index 052fc50..3326c6f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ProjectCheckPage.java @@ -16,7 +16,7 @@ package com.android.ide.eclipse.adt.internal.wizards.export; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; @@ -160,7 +160,7 @@ final class ProjectCheckPage extends ExportWizardPage { mHasMessage = true; } else { try { - if (project.hasNature(AndroidConstants.NATURE_DEFAULT) == false) { + if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) { addError(mErrorComposite, "Project is not an Android project."); } else { // check for errors @@ -173,7 +173,7 @@ final class ProjectCheckPage extends ExportWizardPage { if (outputIFolder != null) { String outputOsPath = outputIFolder.getLocation().toOSString(); String apkFilePath = outputOsPath + File.separator + project.getName() + - AndroidConstants.DOT_ANDROID_PACKAGE; + AdtConstants.DOT_ANDROID_PACKAGE; File f = new File(apkFilePath); if (f.isFile() == false) { @@ -181,7 +181,7 @@ final class ProjectCheckPage extends ExportWizardPage { String.format("%1$s/%2$s/%1$s%3$s does not exists!", project.getName(), outputIFolder.getName(), - AndroidConstants.DOT_ANDROID_PACKAGE)); + AdtConstants.DOT_ANDROID_PACKAGE)); } } else { addError(mErrorComposite, diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java index d17e3cf..c02f773 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreationPage.java @@ -22,8 +22,9 @@ package com.android.ide.eclipse.adt.internal.wizards.newproject; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; @@ -48,6 +49,7 @@ import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.JavaConventions; +import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; @@ -69,11 +71,16 @@ import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkingSet; import java.io.File; import java.io.FileFilter; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; import java.util.regex.Pattern; /** @@ -92,6 +99,8 @@ import java.util.regex.Pattern; * Do not derive from this class. */ public class NewProjectCreationPage extends WizardPage { + /** Suffix added by default to activity names */ + private static final String ACTIVITY_NAME_SUFFIX = "Activity"; //$NON-NLS-1$ // constants private static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$ @@ -154,12 +163,15 @@ public class NewProjectCreationPage extends WizardPage { private boolean mInternalApplicationNameUpdate; private boolean mInternalCreateActivityUpdate; private boolean mInternalActivityNameUpdate; + private boolean mInternalMinSdkUpdate; private boolean mProjectNameModifiedByUser; private boolean mApplicationNameModifiedByUser; + private boolean mActivityNameModifiedByUser; + private boolean mMinSdkModifiedByUser; private final ArrayList<String> mSamplesPaths = new ArrayList<String>(); private Combo mSamplesCombo; - + private WorkingSetGroup mWorkingSetGroup; /** @@ -170,6 +182,12 @@ public class NewProjectCreationPage extends WizardPage { setPageComplete(false); setTitle("New Android Project"); setDescription("Creates a new Android Project resource."); + mWorkingSetGroup = new WorkingSetGroup(); + setWorkingSets(new IWorkingSet[0]); + } + + public void init(IStructuredSelection selection, IWorkbenchPart activePart) { + setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart)); } // --- Getters used by NewProjectWizard --- @@ -212,6 +230,9 @@ public class NewProjectCreationPage extends WizardPage { public String getSourceFolder(); /** Returns the current sdk target or null if none has been selected yet. */ public IAndroidTarget getSdkTarget(); + /** Returns the current working sets or null if none has been selected yet. */ + public IWorkingSet[] getSelectedWorkingSets(); + } @@ -297,6 +318,11 @@ public class NewProjectCreationPage extends WizardPage { public IAndroidTarget getSdkTarget() { return mSdkTargetSelector == null ? null : mSdkTargetSelector.getSelected(); } + + /** Returns the current sdk target or null if none has been selected yet. */ + public IWorkingSet[] getSelectedWorkingSets() { + return getWorkingSets(); + } } /** @@ -354,6 +380,7 @@ public class NewProjectCreationPage extends WizardPage { createLocationGroup(composite); createTargetGroup(composite); createPropertiesGroup(composite); + createWorkingSetGroup(composite); // Update state the first time enableLocationWidgets(); @@ -415,15 +442,11 @@ public class NewProjectCreationPage extends WizardPage { mProjectNameField.setFont(parent.getFont()); mProjectNameField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { - if (!mInternalProjectNameUpdate) { - mProjectNameModifiedByUser = true; - } - updateLocationPathField(null); + onProjectFieldModified(); } }); } - /** * Creates the group for the Project options: * [radio] Create new project @@ -514,7 +537,11 @@ public class NewProjectCreationPage extends WizardPage { new Label(samples_group, SWT.NONE).setText("Samples:"); - mSamplesCombo = new Combo(samples_group, SWT.DROP_DOWN | SWT.READ_ONLY); + if (Platform.getWS().equals(Platform.WS_GTK)) { + mSamplesCombo = new Combo(samples_group, SWT.SIMPLE | SWT.READ_ONLY); + } else { + mSamplesCombo = new Combo(samples_group, SWT.DROP_DOWN | SWT.READ_ONLY); + } mSamplesCombo.setEnabled(false); mSamplesCombo.select(0); mSamplesCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); @@ -615,9 +642,7 @@ public class NewProjectCreationPage extends WizardPage { mApplicationNameField.setFont(parent.getFont()); mApplicationNameField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { - if (!mInternalApplicationNameUpdate) { - mApplicationNameModifiedByUser = true; - } + onApplicationFieldModified(); } }); @@ -678,11 +703,17 @@ public class NewProjectCreationPage extends WizardPage { mMinSdkVersionField.setFont(parent.getFont()); mMinSdkVersionField.addListener(SWT.Modify, new Listener() { public void handleEvent(Event event) { + onMinSdkFieldUpdated(); validatePageComplete(); } }); } + private void createWorkingSetGroup(final Composite composite) { + Composite group = mWorkingSetGroup.createControl(composite); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + } + //--- Internal getters & setters ------------------ @@ -870,6 +901,54 @@ public class NewProjectCreationPage extends WizardPage { } } + private void onProjectFieldModified() { + if (!mInternalProjectNameUpdate) { + mProjectNameModifiedByUser = true; + + if (!mApplicationNameModifiedByUser) { + String name = DescriptorsUtils.capitalize(mProjectNameField.getText()); + try { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(name); + } finally { + mInternalApplicationNameUpdate = false; + } + } + if (!mActivityNameModifiedByUser) { + String name = DescriptorsUtils.capitalize(mProjectNameField.getText()); + try { + mInternalActivityNameUpdate = true; + mActivityNameField.setText(name + ACTIVITY_NAME_SUFFIX); + } finally { + mInternalActivityNameUpdate = false; + } + + } + } + updateLocationPathField(null); + } + + private void onMinSdkFieldUpdated() { + if (!mInternalMinSdkUpdate) { + mMinSdkModifiedByUser = true; + } + } + + private void onApplicationFieldModified() { + if (!mInternalApplicationNameUpdate) { + mApplicationNameModifiedByUser = true; + if (!mActivityNameModifiedByUser) { + String name = DescriptorsUtils.capitalize(mApplicationNameField.getText()); + try { + mInternalActivityNameUpdate = true; + mActivityNameField.setText(name + ACTIVITY_NAME_SUFFIX); + } finally { + mInternalActivityNameUpdate = false; + } + } + } + } + /** * The location path field is either modified internally (from updateLocationPathField) * or manually by the user when the custom_location mode is not set. @@ -928,6 +1007,10 @@ public class NewProjectCreationPage extends WizardPage { * validate the page. */ private void onActivityNameFieldModified() { + if (!mInternalActivityNameUpdate) { + mActivityNameModifiedByUser = true; + } + if (mInfo.isNewProject() && !mInternalActivityNameUpdate) { mUserActivityName = mInfo.getActivityName(); validatePageComplete(); @@ -943,6 +1026,40 @@ public class NewProjectCreationPage extends WizardPage { private void onSdkTargetModified() { IAndroidTarget target = mInfo.getSdkTarget(); + // Update the minimum SDK text field? + // We do if one of two conditions are met: + if (target != null) { + boolean setMinSdk = false; + int apiLevel = target.getVersion().getApiLevel(); + // 1. Has the user not manually edited the SDK field yet? If so, keep + // updating it to the selected value. + if (!mMinSdkModifiedByUser) { + setMinSdk = true; + } else { + // 2. Is the API level set to a higher level than the newly selected + // target SDK? If so, change it down to the new lower value. + String s = mMinSdkVersionField.getText().trim(); + if (s.length() > 0) { + try { + int currentApi = Integer.parseInt(s); + if (currentApi > apiLevel) { + setMinSdk = true; + } + } catch (NumberFormatException nfe) { + // User may have typed something invalid -- ignore + } + } + } + if (setMinSdk) { + try { + mInternalMinSdkUpdate = true; + mMinSdkVersionField.setText(Integer.toString(apiLevel)); + } finally { + mInternalMinSdkUpdate = false; + } + } + } + loadSamplesForTarget(target); enableLocationWidgets(); onSampleSelected(); @@ -1040,7 +1157,7 @@ public class NewProjectCreationPage extends WizardPage { // name as a default. If the activity name has dots, it's a part of a // package specification and only the last identifier must be used. if (activityName.indexOf('.') != -1) { - String[] ids = activityName.split(AndroidConstants.RE_DOT); + String[] ids = activityName.split(AdtConstants.RE_DOT); activityName = ids[ids.length - 1]; } if (mProjectNameField.getText().length() == 0 || !mProjectNameModifiedByUser) { @@ -1178,6 +1295,8 @@ public class NewProjectCreationPage extends WizardPage { mSamplesCombo.add("This target has no samples. Please select another target."); mSamplesCombo.select(0); return; + } else { + Collections.sort(mSamplesPaths); } // Recompute the description of each sample (the relative path @@ -1185,6 +1304,7 @@ public class NewProjectCreationPage extends WizardPage { int selIndex = 0; int i = 0; int n = samplesRootPath.length(); + Set<String> paths = new TreeSet<String>(); for (String path : mSamplesPaths) { if (path.length() > n) { path = path.substring(n); @@ -1201,10 +1321,10 @@ public class NewProjectCreationPage extends WizardPage { selIndex = i; } - mSamplesCombo.add(path); + paths.add(path); i++; } - + mSamplesCombo.setItems(paths.toArray(new String[0])); mSamplesCombo.select(selIndex); } else { @@ -1487,6 +1607,10 @@ public class NewProjectCreationPage extends WizardPage { return setStatus("Activity name must be specified.", MSG_ERROR); } + if (ACTIVITY_NAME_SUFFIX.equals(activityFieldContents)) { + return setStatus("Enter a valid activity name", MSG_ERROR); + } + // The activity field can actually contain part of a sub-package name // or it can start with a dot "." to indicates it comes from the parent package name. String packageName = ""; //$NON-NLS-1$ @@ -1588,7 +1712,7 @@ public class NewProjectCreationPage extends WizardPage { } else if (osTarget.indexOf('.') == 0) { osTarget = mInfo.getPackageName() + osTarget; } - osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA; + osTarget = osTarget.replace('.', File.separatorChar) + AdtConstants.DOT_JAVA; String projectPath = getProjectLocation(); File projectDir = new File(projectPath); @@ -1634,4 +1758,23 @@ public class NewProjectCreationPage extends WizardPage { return messageType; } + /** + * Returns the working sets to which the new project should be added. + * + * @return the selected working sets to which the new project should be added + */ + public IWorkingSet[] getWorkingSets() { + return mWorkingSetGroup.getSelectedWorkingSets(); + } + + /** + * Sets the working sets to which the new project should be added. + * + * @param workingSets the initial selected working sets + */ + public void setWorkingSets(IWorkingSet[] workingSets) { + assert workingSets != null; + mWorkingSetGroup.setWorkingSets(workingSets); + } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java index 7c35ac8..9a3f572 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizard.java @@ -16,18 +16,19 @@ package com.android.ide.eclipse.adt.internal.wizards.newproject; +import com.android.AndroidConstants; import com.android.ide.common.layout.LayoutConstants; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; import com.android.ide.eclipse.adt.internal.project.AndroidNature; import com.android.ide.eclipse.adt.internal.project.ProjectHelper; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage.IMainInfo; import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage.TestInfo; +import com.android.io.StreamException; import com.android.resources.Density; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.StreamException; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -57,8 +58,11 @@ import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Display; import org.eclipse.ui.INewWizard; import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkingSet; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.WorkspaceModifyOperation; import java.io.ByteArrayInputStream; @@ -80,8 +84,8 @@ import java.util.Map.Entry; * Note: this class is public so that it can be accessed from unit tests. * It is however an internal class. Its API may change without notice. * It should semantically be considered as a private final class. + * <p/> * Do not derive from this class. - */ public class NewProjectWizard extends Wizard implements INewWizard { @@ -107,6 +111,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$ private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$ private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$ + private static final String PARAM_IMPORT_RESOURCE_CLASS = "IMPORT_RESOURCE_CLASS"; //$NON-NLS-1$ private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$ private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$ private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$ @@ -130,25 +135,28 @@ public class NewProjectWizard extends Wizard implements INewWizard { private static final String PH_TEST_INSTRUMENTATION = "TEST-INSTRUMENTATION"; //$NON-NLS-1$ private static final String BIN_DIRECTORY = - SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP; + SdkConstants.FD_OUTPUT + AdtConstants.WS_SEP; private static final String RES_DIRECTORY = - SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP; private static final String ASSETS_DIRECTORY = - SdkConstants.FD_ASSETS + AndroidConstants.WS_SEP; + SdkConstants.FD_ASSETS + AdtConstants.WS_SEP; private static final String DRAWABLE_DIRECTORY = - SdkConstants.FD_DRAWABLE + AndroidConstants.WS_SEP; + AndroidConstants.FD_RES_DRAWABLE + AdtConstants.WS_SEP; private static final String DRAWABLE_HDPI_DIRECTORY = - SdkConstants.FD_DRAWABLE + "-" + Density.HIGH.getResourceValue() + AndroidConstants.WS_SEP; //$NON-NLS-1$ + AndroidConstants.FD_RES_DRAWABLE + "-" + Density.HIGH.getResourceValue() + //$NON-NLS-1$ + AdtConstants.WS_SEP; private static final String DRAWABLE_MDPI_DIRECTORY = - SdkConstants.FD_DRAWABLE + "-" + Density.MEDIUM.getResourceValue() + AndroidConstants.WS_SEP; //$NON-NLS-1$ + AndroidConstants.FD_RES_DRAWABLE + "-" + Density.MEDIUM.getResourceValue() + //$NON-NLS-1$ + AdtConstants.WS_SEP; private static final String DRAWABLE_LDPI_DIRECTORY = - SdkConstants.FD_DRAWABLE + "-" + Density.LOW.getResourceValue() + AndroidConstants.WS_SEP; //$NON-NLS-1$ + AndroidConstants.FD_RES_DRAWABLE + "-" + Density.LOW.getResourceValue() + //$NON-NLS-1$ + AdtConstants.WS_SEP; private static final String LAYOUT_DIRECTORY = - SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP; + AndroidConstants.FD_RES_LAYOUT + AdtConstants.WS_SEP; private static final String VALUES_DIRECTORY = - SdkConstants.FD_VALUES + AndroidConstants.WS_SEP; + AndroidConstants.FD_RES_VALUES + AdtConstants.WS_SEP; private static final String GEN_SRC_DIRECTORY = - SdkConstants.FD_GEN_SOURCES + AndroidConstants.WS_SEP; + SdkConstants.FD_GEN_SOURCES + AdtConstants.WS_SEP; private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$ private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY @@ -189,7 +197,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { DRAWABLE_HDPI_DIRECTORY, DRAWABLE_MDPI_DIRECTORY, DRAWABLE_LDPI_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY }; - private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$ + private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$ private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$ private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$ private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$ @@ -416,13 +424,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { parameters.put(PARAM_MIN_SDK_VERSION, info.getMinSdkVersion()); if (info.isCreateActivity()) { - // An activity name can be of the form ".package.Class" or ".Class". - // The initial dot is ignored, as it is always added later in the templates. - String activityName = info.getActivityName(); - if (activityName.startsWith(".")) { //$NON-NLS-1$ - activityName = activityName.substring(1); - } - parameters.put(PARAM_ACTIVITY, activityName); + parameters.put(PARAM_ACTIVITY, info.getActivityName()); } // create a dictionary of string that will contain name+content. @@ -432,7 +434,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { IPath path = info.getLocationPath(); IPath defaultLocation = Platform.getLocation(); - if (!path.equals(defaultLocation)) { + if (path != null && !path.equals(defaultLocation)) { description.setLocation(path); } @@ -574,6 +576,21 @@ public class NewProjectWizard extends Wizard implements INewWizard { mainData.getDescription(), mainData.getParameters(), mainData.getDictionary()); + + if (mainProject != null) { + final IJavaProject javaProject = JavaCore.create(mainProject); + Display.getDefault().syncExec(new Runnable() { + + public void run() { + IWorkingSet[] workingSets = mMainPage.getWorkingSets(); + if (workingSets.length > 0 && javaProject != null + && javaProject.exists()) { + PlatformUI.getWorkbench().getWorkingSetManager() + .addToWorkingSets(javaProject, workingSets); + } + } + }); + } } if (testData != null) { @@ -583,14 +600,27 @@ public class NewProjectWizard extends Wizard implements INewWizard { parameters.put(PARAM_REFERENCE_PROJECT, mainProject); } - createEclipseProject( + IProject testProject = createEclipseProject( new SubProgressMonitor(monitor, 50), testData.getProject(), testData.getDescription(), parameters, testData.getDictionary()); + if (testProject != null) { + final IJavaProject javaProject = JavaCore.create(testProject); + Display.getDefault().syncExec(new Runnable() { + + public void run() { + IWorkingSet[] workingSets = mTestPage.getWorkingSets(); + if (workingSets.length > 0 && javaProject != null + && javaProject.exists()) { + PlatformUI.getWorkbench().getWorkingSetManager() + .addToWorkingSets(javaProject, workingSets); + } + } + }); + } } - } catch (CoreException e) { throw new InvocationTargetException(e); } catch (IOException e) { @@ -635,12 +665,12 @@ public class NewProjectWizard extends Wizard implements INewWizard { AndroidNature.setupProjectNatures(project, monitor); // Create folders in the project if they don't already exist - addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); + addDefaultDirectories(project, AdtConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); String[] sourceFolders = new String[] { (String) parameters.get(PARAM_SRC_FOLDER), GEN_SRC_DIRECTORY }; - addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor); + addDefaultDirectories(project, AdtConstants.WS_ROOT, sourceFolders, monitor); // Create the resource folders in the project if they don't already exist. if (legacy) { @@ -778,6 +808,15 @@ public class NewProjectWizard extends Wizard implements INewWizard { // now get the activity template String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES); + // If the activity name doesn't contain any dot, it's in the form + // "ClassName" and we need to expand it to ".ClassName" in the XML. + String name = (String) parameters.get(PARAM_ACTIVITY); + if (name.indexOf('.') == -1) { + // Duplicate the parameters map to avoid changing the caller + parameters = new HashMap<String, Object>(parameters); + parameters.put(PARAM_ACTIVITY, "." + name); //$NON-NLS-1$ + } + // Replace all keyword parameters to make main activity. String activities = replaceParameters(activityTemplate, parameters); @@ -856,8 +895,8 @@ public class NewProjectWizard extends Wizard implements INewWizard { throws CoreException, IOException { // create the IFile object and check if the file doesn't already exist. - IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + VALUES_DIRECTORY + AndroidConstants.WS_SEP + STRINGS_FILE); + IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP + + VALUES_DIRECTORY + AdtConstants.WS_SEP + STRINGS_FILE); if (!file.exists()) { // get the Strings.xml template String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS); @@ -909,8 +948,8 @@ public class NewProjectWizard extends Wizard implements INewWizard { throws CoreException { if (legacy) { // density support // do medium density icon only, in the default drawable folder. - IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + DRAWABLE_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON); + IFile file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP + + DRAWABLE_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON); if (!file.exists()) { addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor); } @@ -919,22 +958,22 @@ public class NewProjectWizard extends Wizard implements INewWizard { IFile file; // high density - file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + DRAWABLE_HDPI_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON); + file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP + + DRAWABLE_HDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON); if (!file.exists()) { addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_HDPI), monitor); } // medium density - file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + DRAWABLE_MDPI_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON); + file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP + + DRAWABLE_MDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON); if (!file.exists()) { addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_MDPI), monitor); } // low density - file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + DRAWABLE_LDPI_DIRECTORY + AndroidConstants.WS_SEP + PROJECT_ICON); + file = project.getFile(RES_DIRECTORY + AdtConstants.WS_SEP + + DRAWABLE_LDPI_DIRECTORY + AdtConstants.WS_SEP + PROJECT_ICON); if (!file.exists()) { addFile(file, AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON_LDPI), monitor); } @@ -978,25 +1017,45 @@ public class NewProjectWizard extends Wizard implements INewWizard { // The PARAM_ACTIVITY key will be absent if no activity should be created, // in which case activityName will be null. String activityName = (String) parameters.get(PARAM_ACTIVITY); - Map<String, Object> java_activity_parameters = parameters; + + Map<String, Object> java_activity_parameters = new HashMap<String, Object>(parameters); + java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, ""); //$NON-NLS-1$ + if (activityName != null) { - if (activityName.indexOf('.') >= 0) { - // There are package names in the activity name. Transform packageName to add - // those sub packages and remove them from activityName. - packageName += "." + activityName; //$NON-NLS-1$ - int pos = packageName.lastIndexOf('.'); - activityName = packageName.substring(pos + 1); - packageName = packageName.substring(0, pos); - - // Also update the values used in the JAVA_FILE_TEMPLATE below - // (but not the ones from the manifest so don't change the caller's dictionary) - java_activity_parameters = new HashMap<String, Object>(parameters); - java_activity_parameters.put(PARAM_PACKAGE, packageName); - java_activity_parameters.put(PARAM_ACTIVITY, activityName); + + String resourcePackageClass = null; + + // An activity name can be of the form ".package.Class", ".Class" or FQDN. + // The initial dot is ignored, as it is always added later in the templates. + int lastDotIndex = activityName.lastIndexOf('.'); + + if (lastDotIndex != -1) { + + // Resource class + if (lastDotIndex > 0) { + resourcePackageClass = packageName + "." + AdtConstants.FN_RESOURCE_BASE; //$NON-NLS-1$ + } + + // Package name + if (activityName.startsWith(".")) { //$NON-NLS-1$ + packageName += activityName.substring(0, lastDotIndex); + } else { + packageName = activityName.substring(0, lastDotIndex); + } + + // Activity Class name + activityName = activityName.substring(lastDotIndex + 1); + } + + java_activity_parameters.put(PARAM_ACTIVITY, activityName); + java_activity_parameters.put(PARAM_PACKAGE, packageName); + if (resourcePackageClass != null) { + String importResourceClass = "\nimport " + resourcePackageClass + ";"; //$NON-NLS-1$ // $NON-NLS-2$ + java_activity_parameters.put(PARAM_IMPORT_RESOURCE_CLASS, importResourceClass); } } - String[] components = packageName.split(AndroidConstants.RE_DOT); + String[] components = packageName.split(AdtConstants.RE_DOT); for (String component : components) { pkgFolder = pkgFolder.getFolder(component); if (!pkgFolder.exists()) { @@ -1007,7 +1066,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { if (activityName != null) { // create the main activity Java file - String activityJava = activityName + AndroidConstants.DOT_JAVA; + String activityJava = activityName + AdtConstants.DOT_JAVA; IFile file = pkgFolder.getFile(activityJava); if (!file.exists()) { copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor); @@ -1047,7 +1106,7 @@ public class NewProjectWizard extends Wizard implements INewWizard { * Adds the given folder to the project's class path. * * @param javaProject The Java Project to update. - * @param sourceFolder Template Parameters. + * @param sourceFolders Template Parameters. * @param monitor An existing monitor. * @throws JavaModelException if the classpath could not be set. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java index fe24553..dfdd72e 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewTestProjectCreationPage.java @@ -45,6 +45,7 @@ import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaConventions; +import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; @@ -67,6 +68,8 @@ import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkingSet; import java.io.File; import java.net.URI; @@ -86,6 +89,7 @@ import java.util.regex.Pattern; * Note: this class is public so that it can be accessed from unit tests. * It is however an internal class. Its API may change without notice. * It should semantically be considered as a private final class. + * <p/> * Do not derive from this class. */ public class NewTestProjectCreationPage extends WizardPage { @@ -155,6 +159,7 @@ public class NewTestProjectCreationPage extends WizardPage { private Label mTestTargetPackageLabel; private String mLastExistingPackageName; + private WorkingSetGroup mWorkingSetGroup; /** @@ -165,6 +170,12 @@ public class NewTestProjectCreationPage extends WizardPage { setPageComplete(false); setTitle("New Android Test Project"); setDescription("Creates a new Android Test Project resource."); + mWorkingSetGroup= new WorkingSetGroup(); + setWorkingSets(new IWorkingSet[0]); + } + + public void init(IStructuredSelection selection, IWorkbenchPart activePart) { + setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart)); } // --- Getters used by NewProjectWizard --- @@ -298,6 +309,7 @@ public class NewTestProjectCreationPage extends WizardPage { createTestTargetGroup(composite); createTargetGroup(composite); createPropertiesGroup(composite); + createWorkingSetGroup(composite); // Update state the first time enableLocationWidgets(); @@ -693,6 +705,12 @@ public class NewTestProjectCreationPage extends WizardPage { }); } + private void createWorkingSetGroup(final Composite composite) { + Composite group = mWorkingSetGroup.createControl(composite); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + mToggleComposites.add(group); + } //--- Internal getters & setters ------------------ @@ -746,6 +764,7 @@ public class NewTestProjectCreationPage extends WizardPage { */ private void useMainProjectInformation() { if (mInfo.isTestingMain() && mMainInfo != null) { + useMainWorkingSets(); String projName = String.format("%1$sTest", mMainInfo.getProjectName()); String appName = String.format("%1$sTest", mMainInfo.getApplicationName()); @@ -790,6 +809,17 @@ public class NewTestProjectCreationPage extends WizardPage { } } + private void useMainWorkingSets() { + IWorkingSet[] workingSets = mMainInfo.getSelectedWorkingSets(); + if (workingSets != null) { + // getSelectedWorkingSets returns an empty list if the working set feature is disabled. + if (workingSets.length > 0) { + mWorkingSetGroup.setChecked(true); + } + mWorkingSetGroup.setWorkingSets(workingSets); + } + } + /** * Callback invoked when the user edits the project text field. */ @@ -1342,4 +1372,24 @@ public class NewTestProjectCreationPage extends WizardPage { return messageType; } + /** + * Returns the working sets to which the new project should be added. + * + * @return the selected working sets to which the new project should be added + */ + public IWorkingSet[] getWorkingSets() { + return mWorkingSetGroup.getSelectedWorkingSets(); + } + + /** + * Sets the working sets to which the new project should be added. + * + * @param workingSets the initial selected working sets + */ + public void setWorkingSets(IWorkingSet[] workingSets) { + assert workingSets != null; + mWorkingSetGroup.setWorkingSets(workingSets); + } + + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java new file mode 100644 index 0000000..fb33a08 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetGroup.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package com.android.ide.eclipse.adt.internal.wizards.newproject; + +import org.eclipse.jdt.internal.ui.JavaPlugin; +import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages; +import org.eclipse.jdt.internal.ui.workingsets.IWorkingSetIDs; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.ui.IWorkingSet; +import org.eclipse.ui.dialogs.WorkingSetConfigurationBlock; + +/** + * Copied from + * org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageOne$WorkingSetGroup + * + * Creates the working set group with controls that allow + * the selection of working sets + */ +@SuppressWarnings("restriction") +public class WorkingSetGroup { + + private WorkingSetConfigurationBlock fWorkingSetBlock; + private Button mEnableButton; + + public WorkingSetGroup() { + String[] workingSetIds = new String[] { + IWorkingSetIDs.JAVA, IWorkingSetIDs.RESOURCE + }; + fWorkingSetBlock = new WorkingSetConfigurationBlock(workingSetIds, JavaPlugin.getDefault() + .getDialogSettings()); + } + + public Composite createControl(Composite composite) { + Group workingSetGroup = new Group(composite, SWT.NONE); + workingSetGroup.setFont(composite.getFont()); + workingSetGroup.setText(NewWizardMessages.NewJavaProjectWizardPageOne_WorkingSets_group); + workingSetGroup.setLayout(new GridLayout(1, false)); + + fWorkingSetBlock.createContent(workingSetGroup); + + // WorkingSetGroup is implemented in such a way that the checkbox it contains + // can only be programmatically set if there's an existing working set associated + // *before* we construct the control. However the control is created when the + // wizard is opened, not when the page is first shown. + // + // One choice is to duplicate the class in our project. + // Or find the checkbox we want and trigger it manually. + mEnableButton = findCheckbox(workingSetGroup); + + return workingSetGroup; + } + + public void setWorkingSets(IWorkingSet[] workingSets) { + fWorkingSetBlock.setWorkingSets(workingSets); + } + + public IWorkingSet[] getSelectedWorkingSets() { + try { + return fWorkingSetBlock.getSelectedWorkingSets(); + } catch (Throwable t) { + // Test scenarios; no UI is created, which the fWorkingSetBlock assumes + // (it dereferences the enabledButton) + return new IWorkingSet[0]; + } + } + + public boolean isChecked() { + return mEnableButton == null ? false : mEnableButton.getSelection(); + } + + public void setChecked(boolean state) { + if (mEnableButton != null) { + mEnableButton.setSelection(state); + } + } + + /** + * Finds the first button of style Checkbox in the given parent composite. + * Returns null if not found. + */ + private Button findCheckbox(Composite parent) { + for (Control control : parent.getChildren()) { + if (control instanceof Button && (control.getStyle() & SWT.CHECK) == SWT.CHECK) { + return (Button) control; + } else if (control instanceof Composite) { + Button found = findCheckbox((Composite) control); + if (found != null) { + return found; + } + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java new file mode 100755 index 0000000..428bfd3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/WorkingSetHelper.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package com.android.ide.eclipse.adt.internal.wizards.newproject; + +import org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart; +import org.eclipse.jdt.internal.ui.workingsets.IWorkingSetIDs; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkingSet; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * This class contains a helper method to deal with working sets. + * <p/> + * Copied from org.eclipse.jdt.ui.wizards.NewJavaProjectWizardPageOne + */ +@SuppressWarnings("restriction") +public final class WorkingSetHelper { + + private static final IWorkingSet[] EMPTY_WORKING_SET_ARRAY = new IWorkingSet[0]; + + /** This class is never instantiated. */ + private WorkingSetHelper() { + } + + public static IWorkingSet[] getSelectedWorkingSet(IStructuredSelection selection, + IWorkbenchPart activePart) { + IWorkingSet[] selected= getSelectedWorkingSet(selection); + if (selected != null && selected.length > 0) { + for (int i= 0; i < selected.length; i++) { + if (!isValidWorkingSet(selected[i])) + return EMPTY_WORKING_SET_ARRAY; + } + return selected; + } + + if (!(activePart instanceof PackageExplorerPart)) + return EMPTY_WORKING_SET_ARRAY; + + PackageExplorerPart explorerPart= (PackageExplorerPart) activePart; + if (explorerPart.getRootMode() == PackageExplorerPart.PROJECTS_AS_ROOTS) { + //Get active filter + IWorkingSet filterWorkingSet= explorerPart.getFilterWorkingSet(); + if (filterWorkingSet == null) + return EMPTY_WORKING_SET_ARRAY; + + if (!isValidWorkingSet(filterWorkingSet)) + return EMPTY_WORKING_SET_ARRAY; + + return new IWorkingSet[] {filterWorkingSet}; + } else { + //If we have been gone into a working set return the working set + Object input= explorerPart.getViewPartInput(); + if (!(input instanceof IWorkingSet)) + return EMPTY_WORKING_SET_ARRAY; + + IWorkingSet workingSet= (IWorkingSet)input; + if (!isValidWorkingSet(workingSet)) + return EMPTY_WORKING_SET_ARRAY; + + return new IWorkingSet[] {workingSet}; + } + } + + private static IWorkingSet[] getSelectedWorkingSet(IStructuredSelection selection) { + if (!(selection instanceof ITreeSelection)) + return EMPTY_WORKING_SET_ARRAY; + + ITreeSelection treeSelection= (ITreeSelection) selection; + if (treeSelection.isEmpty()) + return EMPTY_WORKING_SET_ARRAY; + + List<?> elements = treeSelection.toList(); + if (elements.size() == 1) { + Object element= elements.get(0); + TreePath[] paths= treeSelection.getPathsFor(element); + if (paths.length != 1) + return EMPTY_WORKING_SET_ARRAY; + + TreePath path= paths[0]; + if (path.getSegmentCount() == 0) + return EMPTY_WORKING_SET_ARRAY; + + Object candidate= path.getSegment(0); + if (!(candidate instanceof IWorkingSet)) + return EMPTY_WORKING_SET_ARRAY; + + IWorkingSet workingSetCandidate= (IWorkingSet) candidate; + if (isValidWorkingSet(workingSetCandidate)) + return new IWorkingSet[] { workingSetCandidate }; + + return EMPTY_WORKING_SET_ARRAY; + } + + ArrayList<Object> result = new ArrayList<Object>(); + for (Iterator<?> iterator = elements.iterator(); iterator.hasNext();) { + Object element= iterator.next(); + if (element instanceof IWorkingSet && isValidWorkingSet((IWorkingSet) element)) { + result.add(element); + } + } + return result.toArray(new IWorkingSet[result.size()]); + } + + + private static boolean isValidWorkingSet(IWorkingSet workingSet) { + String id= workingSet.getId(); + if (!IWorkingSetIDs.JAVA.equals(id) && !IWorkingSetIDs.RESOURCE.equals(id)) + return false; + + if (workingSet.isAggregateWorkingSet()) + return false; + + return true; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java index 03aed88..c4f86a6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java @@ -17,8 +17,17 @@ package com.android.ide.eclipse.adt.internal.wizards.newxmlfile; +import static com.android.ide.common.layout.LayoutConstants.HORIZONTAL_SCROLL_VIEW; +import static com.android.ide.common.layout.LayoutConstants.SCROLL_VIEW; +import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; + +import com.android.AndroidConstants; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider; @@ -27,15 +36,14 @@ import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.Resour import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.ConfigurationState; import com.android.ide.eclipse.adt.internal.ui.ConfigurationSelector.SelectorMode; +import com.android.resources.ResourceFolderType; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; import com.android.util.Pair; @@ -65,6 +73,11 @@ import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.FileEditorInput; import java.util.ArrayList; import java.util.Collections; @@ -203,6 +216,18 @@ class NewXmlFileCreationPage extends WizardPage { } /** + * When not null, represents an extra string that should be written inside + * the element when constructed + * + * @param project the project to get the child content for + * @param root the chosen root element + * @return a string to be written inside the root element, or null if nothing + */ + String getChild(IProject project, String root) { + return null; + } + + /** * The minimum API level required by the current SDK target to support this feature. * * @return the minimum API level @@ -232,17 +257,31 @@ class NewXmlFileCreationPage extends WizardPage { @Override String getDefaultAttrs(IProject project) { Sdk currentSdk = Sdk.getCurrent(); + String fill = VALUE_FILL_PARENT; if (currentSdk != null) { IAndroidTarget target = currentSdk.getTarget(project); // fill_parent was renamed match_parent in API level 8 if (target != null && target.getVersion().getApiLevel() >= 8) { - return "android:layout_width=\"match_parent\"\n" //$NON-NLS-1$ - + "android:layout_height=\"match_parent\""; //$NON-NLS-1$ + fill = VALUE_MATCH_PARENT; } } - return "android:layout_width=\"fill_parent\"\n" //$NON-NLS-1$ - + "android:layout_height=\"fill_parent\""; //$NON-NLS-1$ + return String.format( + "android:orientation=\"vertical\"\n" //$NON-NLS-1$ + + "android:layout_width=\"%1$s\"\n" //$NON-NLS-1$ + + "android:layout_height=\"%1$s\"", //$NON-NLS-1$ + fill, fill); + } + + @Override + String getChild(IProject project, String root) { + // Create vertical linear layouts inside new scroll views + if (SCROLL_VIEW.equals(root) || HORIZONTAL_SCROLL_VIEW.equals(root)) { + return " <LinearLayout " //$NON-NLS-1$ + + getDefaultAttrs(project).replace('\n', ' ') + + "></LinearLayout>\n"; //$NON-NLS-1$ + } + return null; } }, new TypeInfo("Values", // UI name @@ -254,6 +293,15 @@ class NewXmlFileCreationPage extends WizardPage { null, // default attributes 1 // target API level ), + new TypeInfo("Drawable", // UI name + "An XML file that describes a drawable.", // tooltip + ResourceFolderType.DRAWABLE, // folder type + AndroidTargetData.DESCRIPTOR_DRAWABLE, // root seed + null, // default root + SdkConstants.NS_RESOURCES, // xmlns + null, // default attributes + 1 // target API level + ), new TypeInfo("Menu", // UI name "An XML file that describes an menu.", // tooltip ResourceFolderType.MENU, // folder type @@ -263,6 +311,33 @@ class NewXmlFileCreationPage extends WizardPage { null, // default attributes 1 // target API level ), + new TypeInfo("Color List", // UI name + "An XML file that describes a color state list.", // tooltip + ResourceFolderType.COLOR, // folder type + AndroidTargetData.DESCRIPTOR_COLOR, // root seed + null, // default root + SdkConstants.NS_RESOURCES, // xmlns + null, // default attributes + 1 // target API level + ), + new TypeInfo("Animator", // UI name + "An XML file that describes an animator.", // tooltip + ResourceFolderType.ANIMATOR, // folder type + AndroidTargetData.DESCRIPTOR_ANIMATOR, // root seed + "set", // default root + SdkConstants.NS_RESOURCES, // xmlns + null, // default attributes + 11 // target API level + ), + new TypeInfo("Animation", // UI name + "An XML file that describes an animation.", // tooltip + ResourceFolderType.ANIM, // folder type + AndroidTargetData.DESCRIPTOR_ANIM, // root seed + "set", // default root + null, // xmlns + null, // default attributes + 1 // target API level + ), new TypeInfo("AppWidget Provider", // UI name "An XML file that describes a widget provider.", // tooltip ResourceFolderType.XML, // folder type @@ -290,31 +365,15 @@ class NewXmlFileCreationPage extends WizardPage { null, // default attributes 1 // target API level ), - new TypeInfo("Animation", // UI name - "An XML file that describes an animation.", // tooltip - ResourceFolderType.ANIM, // folder type - // TODO reuse constants if we ever make an editor with descriptors for animations - new String[] { // root seed - "set", //$NON-NLS-1$ - "alpha", //$NON-NLS-1$ - "scale", //$NON-NLS-1$ - "translate", //$NON-NLS-1$ - "rotate" //$NON-NLS-1$ - }, - "set", //$NON-NLS-1$ // default root - null, // xmlns - null, // default attributes - 1 // target API level - ), }; /** Number of columns in the grid layout */ final static int NUM_COL = 4; /** Absolute destination folder root, e.g. "/res/" */ - private static final String RES_FOLDER_ABS = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; + private static final String RES_FOLDER_ABS = AdtConstants.WS_RESOURCES + AdtConstants.WS_SEP; /** Relative destination folder root, e.g. "res/" */ - private static final String RES_FOLDER_REL = SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + private static final String RES_FOLDER_REL = SdkConstants.FD_RESOURCES + AdtConstants.WS_SEP; private IProject mProject; private Text mProjectTextField; @@ -432,7 +491,7 @@ class NewXmlFileCreationPage extends WizardPage { } else { fileName = mFileNameTextField.getText().trim(); if (fileName.length() > 0 && fileName.indexOf('.') == -1) { - fileName = fileName + AndroidConstants.DOT_XML; + fileName = fileName + AdtConstants.DOT_XML; } } @@ -642,7 +701,7 @@ class NewXmlFileCreationPage extends WizardPage { }; int n = sTypes.length; - int num_lines = (n + NUM_COL/2) / NUM_COL; + int num_lines = (n + (NUM_COL - 1)) / NUM_COL; for (int line = 0, k = 0; line < num_lines; line++) { for (int i = 0; i < NUM_COL; i++, k++) { if (k < n) { @@ -754,7 +813,7 @@ class NewXmlFileCreationPage extends WizardPage { // Is this an Android project? try { - if (project == null || !project.hasNature(AndroidConstants.NATURE_DEFAULT)) { + if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) { continue; } } catch (CoreException e) { @@ -804,6 +863,25 @@ class NewXmlFileCreationPage extends WizardPage { } if (targetProject == null) { + // Try to figure out the project from the active editor + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (window != null) { + IWorkbenchPage page = window.getActivePage(); + if (page != null) { + IEditorPart activeEditor = page.getActiveEditor(); + if (activeEditor instanceof AndroidXmlEditor) { + Object input = ((AndroidXmlEditor) activeEditor).getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + targetScore = 1; + targetProject = fileInput.getFile().getProject(); + } + } + } + } + } + + if (targetProject == null) { // If we didn't find a default project based on the selection, check how many // open Android projects we can find in the current workspace. If there's only // one, we'll just select it by default. @@ -1017,12 +1095,12 @@ class NewXmlFileCreationPage extends WizardPage { if (wsFolderPath.startsWith(RES_FOLDER_ABS)) { wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length()); - int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR); + int pos = wsFolderPath.indexOf(AdtConstants.WS_SEP_CHAR); if (pos >= 0) { wsFolderPath = wsFolderPath.substring(0, pos); } - String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP); + String[] folderSegments = wsFolderPath.split(AndroidConstants.RES_QUALIFIER_SEP); if (folderSegments.length > 0) { String folderName = folderSegments[0]; @@ -1040,19 +1118,6 @@ class NewXmlFileCreationPage extends WizardPage { } } - // For now, treat a selection of /res/animator as /res/anim/, - // though we need to handle this better - // TODO: Properly support ANIMATOR templates! - if (!selected && folderName.equals(SdkConstants.FD_ANIMATOR)) { - for (TypeInfo type : sTypes) { - if (type.getResFolderType() == ResourceFolderType.ANIM) { - matches.add(type); - selected |= type.getWidget().getSelection(); - break; - } - } - } - if (matches.size() == 1) { // If there's only one match, select it if it's not already selected if (!selected) { @@ -1255,12 +1320,6 @@ class NewXmlFileCreationPage extends WizardPage { error = "Please select an Android project."; } - // -- validate filename - if (error == null) { - String fileName = getFileName(); - error = ResourceNameValidator.create(true).isValid(fileName); - } - // -- validate type if (error == null) { TypeInfo type = getSelectedType(); @@ -1270,6 +1329,13 @@ class NewXmlFileCreationPage extends WizardPage { } } + // -- validate filename + if (error == null) { + String fileName = getFileName(); + ResourceFolderType folderType = getSelectedType().getResFolderType(); + error = ResourceNameValidator.create(true, folderType).isValid(fileName); + } + // -- validate type API level if (error == null) { IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); @@ -1328,4 +1394,20 @@ class NewXmlFileCreationPage extends WizardPage { } } + /** + * Returns the {@link TypeInfo} for the given {@link ResourceFolderType}, or null if + * not found + * + * @param folderType the {@link ResourceFolderType} to look for + * @return the corresponding {@link TypeInfo} + */ + static TypeInfo getTypeInfo(ResourceFolderType folderType) { + for (TypeInfo typeInfo : sTypes) { + if (typeInfo.getResFolderType() == folderType) { + return typeInfo; + } + } + + return null; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java index a87400b..442568f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java @@ -21,23 +21,24 @@ package com.android.ide.eclipse.adt.internal.wizards.newxmlfile; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileCreationPage.TypeInfo; +import com.android.resources.ResourceFolderType; +import com.android.util.Pair; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; import org.eclipse.ui.INewWizard; import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.ide.IDE; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -51,8 +52,9 @@ import java.io.UnsupportedEncodingException; * the resource folder, resource type and file name. It then creates the XML file. */ public class NewXmlFileWizard extends Wizard implements INewWizard { + public static final String XML_HEADER_LINE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; //$NON-NLS-1$ - private static final String PROJECT_LOGO_LARGE = "android_large"; //$NON-NLS-1$ + private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ protected static final String MAIN_PAGE_NAME = "newAndroidXmlFilePage"; //$NON-NLS-1$ @@ -105,22 +107,18 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { */ @Override public boolean performFinish() { - IFile file = createXmlFile(); - if (file == null) { + Pair<IFile, IRegion> created = createXmlFile(); + if (created == null) { return false; } else { - // Open the file in an editor - IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (win != null) { - IWorkbenchPage page = win.getActivePage(); - if (page != null) { - try { - IDE.openEditor(page, file); - } catch (PartInitException e) { - AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$ - file.getFullPath().toString()); - } - } + IFile file = created.getFirst(); + + // Open the file + try { + AdtPlugin.openFile(file, null, false /* showEditorTab */); + } catch (PartInitException e) { + AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$ + file.getFullPath().toString()); } return true; } @@ -128,25 +126,12 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { // -- Custom Methods -- - private IFile createXmlFile() { + private Pair<IFile, IRegion> createXmlFile() { IFile file = mMainPage.getDestinationFile(); - String name = file.getFullPath().toString(); - boolean need_delete = false; - - if (file.exists()) { - if (!AdtPlugin.displayPrompt("New Android XML File", - String.format("Do you want to overwrite the file %1$s ?", name))) { - // abort if user selects cancel. - return null; - } - need_delete = true; - } else { - createWsParentDirectory(file.getParent()); - } - TypeInfo type = mMainPage.getSelectedType(); if (type == null) { // this is not expected to happen + String name = file.getFullPath().toString(); AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing type", name); //$NON-NLS-1$ return null; } @@ -159,20 +144,52 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { return null; } - StringBuilder sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$ + String attrs = type.getDefaultAttrs(mMainPage.getProject()); + + String child = type.getChild(mMainPage.getProject(), root); + return createXmlFile(file, xmlns, root, attrs, child); + } + + /** Creates a new file using the given root element, namespace and root attributes */ + private static Pair<IFile, IRegion> createXmlFile(IFile file, String xmlns, + String root, String rootAttributes, String child) { + String name = file.getFullPath().toString(); + boolean need_delete = false; + + if (file.exists()) { + if (!AdtPlugin.displayPrompt("New Android XML File", + String.format("Do you want to overwrite the file %1$s ?", name))) { + // abort if user selects cancel. + return null; + } + need_delete = true; + } else { + createWsParentDirectory(file.getParent()); + } + + StringBuilder sb = new StringBuilder(XML_HEADER_LINE); sb.append('<').append(root); if (xmlns != null) { sb.append('\n').append(" xmlns:android=\"").append(xmlns).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ } - String attrs = type.getDefaultAttrs(mMainPage.getProject()); - if (attrs != null) { + if (rootAttributes != null) { sb.append("\n "); //$NON-NLS-1$ - sb.append(attrs.replace("\n", "\n ")); //$NON-NLS-1$ //$NON-NLS-2$ + sb.append(rootAttributes.replace("\n", "\n ")); //$NON-NLS-1$ //$NON-NLS-2$ } sb.append(">\n"); //$NON-NLS-1$ + + if (child != null) { + sb.append(child); + } + + // The insertion line + sb.append(" "); //$NON-NLS-1$ + int caretOffset = sb.length(); + sb.append("\n"); //$NON-NLS-1$ + sb.append("</").append(root).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$ String result = sb.toString(); @@ -184,7 +201,8 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { file.delete(IResource.KEEP_HISTORY | IResource.FORCE, null /*monitor*/); } file.create(stream, true /*force*/, null /*progress*/); - return file; + IRegion region = new Region(caretOffset, 0); + return Pair.of(file, region); } catch (UnsupportedEncodingException e) { error = e.getMessage(); } catch (CoreException e) { @@ -196,7 +214,40 @@ public class NewXmlFileWizard extends Wizard implements INewWizard { return null; } - private boolean createWsParentDirectory(IContainer wsPath) { + /** + * Returns true if the New XML Wizard can create new files of the given + * {@link ResourceFolderType} + * + * @param folderType the folder type to create a file for + * @return true if this wizard can create new files for the given folder type + */ + public static boolean canCreateXmlFile(ResourceFolderType folderType) { + TypeInfo typeInfo = NewXmlFileCreationPage.getTypeInfo(folderType); + return typeInfo != null && (typeInfo.getDefaultRoot() != null || + typeInfo.getRootSeed() instanceof String); + } + + /** + * Creates a new XML file using the template according to the given folder type + * + * @param project the project to create the file in + * @param file the file to be created + * @param folderType the type of folder to look up a template for + * @return the created file + */ + public static Pair<IFile, IRegion> createXmlFile(IProject project, IFile file, + ResourceFolderType folderType) { + TypeInfo type = NewXmlFileCreationPage.getTypeInfo(folderType); + String xmlns = type.getXmlns(); + String root = type.getDefaultRoot(); + if (root == null) { + root = type.getRootSeed().toString(); + } + String attrs = type.getDefaultAttrs(project); + return createXmlFile(file, xmlns, root, attrs, null); + } + + private static boolean createWsParentDirectory(IContainer wsPath) { if (wsPath.getType() == IResource.FOLDER) { if (wsPath == null || wsPath.exists()) { return true; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java index a444cdc..b4e7a3f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFileWrapper.java @@ -16,9 +16,9 @@ package com.android.ide.eclipse.adt.io; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.StreamException; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.StreamException; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -97,6 +97,10 @@ public class IFileWrapper implements IAbstractFile { return mFile; } + public long getModificationStamp() { + return mFile.getModificationStamp(); + } + @Override public boolean equals(Object obj) { if (obj instanceof IFileWrapper) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFolderWrapper.java index 3c00485..85106c2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFolderWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/io/IFolderWrapper.java @@ -16,9 +16,9 @@ package com.android.ide.eclipse.adt.io; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.IAbstractResource; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.IAbstractResource; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/activity.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/activity.template index e91d602..ee17fb8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/activity.template +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/activity.template @@ -1,7 +1,7 @@ - <activity android:name=".ACTIVITY_NAME" + <activity android:name="ACTIVITY_NAME" android:label="APPLICATION_NAME"> <intent-filter> <action android:name="android.intent.action.MAIN" /> INTENT_FILTERS </intent-filter> - </activity>
\ No newline at end of file + </activity> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/java_file.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/java_file.template index 173ff96..e40e77e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/java_file.template +++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/java_file.template @@ -1,5 +1,5 @@ package PACKAGE; - +IMPORT_RESOURCE_CLASS import android.app.Activity; import android.os.Bundle; diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/.settings/org.eclipse.jdt.core.prefs b/eclipse/plugins/com.android.ide.eclipse.ddms/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF index 36bd69c..1b39d70 100644 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Dalvik Debug Monitor Service Bundle-SymbolicName: com.android.ide.eclipse.ddms;singleton:=true -Bundle-Version: 10.0.0.qualifier +Bundle-Version: 11.0.0.qualifier Bundle-Activator: com.android.ide.eclipse.ddms.DdmsPlugin Bundle-Vendor: The Android Open Source Project Bundle-Localization: plugin diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/about.ini b/eclipse/plugins/com.android.ide.eclipse.ddms/about.ini index 560e475..3395379 100644 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/about.ini +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/about.ini @@ -1,2 +1,2 @@ aboutText=%blurb -featureImage=icons/android_32x32.png
\ No newline at end of file +featureImage=icons/ddms-32.png
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/android_32x32.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/android_32x32.png Binary files differdeleted file mode 100644 index a382aad..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/android_32x32.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/ddms-16.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/ddms-16.png Binary files differnew file mode 100644 index 0000000..f40e720 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/ddms-16.png diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/ddms-32.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/ddms-32.png Binary files differnew file mode 100644 index 0000000..d54ab7f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/ddms-32.png diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator-16.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator-16.png Binary files differnew file mode 100644 index 0000000..7554b24 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator-16.png diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png b/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png Binary files differdeleted file mode 100644 index a718042..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/icons/emulator.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml index 9bc5a0c..cc47384 100644 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml @@ -60,7 +60,7 @@ allowMultiple="false" category="com.android.ide.eclipse.ddms.views.category" class="com.android.ide.eclipse.ddms.views.EmulatorControlView" - icon="icons/emulator.png" + icon="icons/emulator-16.png" id="com.android.ide.eclipse.ddms.views.EmulatorControlView" name="Emulator Control"/> <view @@ -75,7 +75,7 @@ point="org.eclipse.ui.perspectives"> <perspective class="com.android.ide.eclipse.ddms.Perspective" - icon="icons/android.png" + icon="icons/ddms-16.png" id="com.android.ide.eclipse.ddms.Perspective" name="DDMS"/> </extension> diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/FileExplorerView.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/FileExplorerView.java index b3e08a2..57e5370 100644 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/FileExplorerView.java +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/views/FileExplorerView.java @@ -110,8 +110,18 @@ public class FileExplorerView extends ViewPart implements ISelectionListener { deleteAction.setImageDescriptor(loader.loadDescriptor("delete.png")); //$NON-NLS-1$ deleteAction.setEnabled(false); + CommonAction createNewFolderAction = new CommonAction("New Folder") { + @Override + public void run() { + mExplorer.createNewFolderInSelection(); + } + }; + createNewFolderAction.setToolTipText("New Folder"); + createNewFolderAction.setImageDescriptor(loader.loadDescriptor("add.png")); //$NON-NLS-1$ + createNewFolderAction.setEnabled(false); + // set up the actions in the explorer - mExplorer.setActions(pushAction, pullAction, deleteAction); + mExplorer.setActions(pushAction, pullAction, deleteAction, createNewFolderAction); // and in the ui IActionBars actionBars = getViewSite().getActionBars(); @@ -122,11 +132,15 @@ public class FileExplorerView extends ViewPart implements ISelectionListener { menuManager.add(pushAction); menuManager.add(new Separator()); menuManager.add(deleteAction); + menuManager.add(new Separator()); + menuManager.add(createNewFolderAction); toolBarManager.add(pullAction); toolBarManager.add(pushAction); toolBarManager.add(new Separator()); toolBarManager.add(deleteAction); + toolBarManager.add(new Separator()); + toolBarManager.add(createNewFolderAction); mExplorer.createPanel(parent); diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/.settings/org.eclipse.jdt.core.prefs b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/META-INF/MANIFEST.MF index 75e828b..d494e48 100644 --- a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Hierarchy Viewer Bundle-SymbolicName: com.android.ide.eclipse.hierarchyviewer;singleton:=true -Bundle-Version: 10.0.0.qualifier +Bundle-Version: 11.0.0.qualifier Bundle-Activator: com.android.ide.eclipse.hierarchyviewer.HierarchyViewerPlugin Bundle-Vendor: The Android Open Source Project Bundle-Localization: plugin diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/about.ini b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/about.ini index 560e475..18f6ab0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/about.ini +++ b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/about.ini @@ -1,2 +1,2 @@ aboutText=%blurb -featureImage=icons/android_32x32.png
\ No newline at end of file +featureImage=icons/hierarchyviewer-32.png
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/android_32x32.png b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/android_32x32.png Binary files differdeleted file mode 100644 index a382aad..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/android_32x32.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/hierarchyviewer-16.png b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/hierarchyviewer-16.png Binary files differnew file mode 100644 index 0000000..02073d4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/hierarchyviewer-16.png diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/hierarchyviewer-32.png b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/hierarchyviewer-32.png Binary files differnew file mode 100644 index 0000000..fa21c24 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/icons/hierarchyviewer-32.png diff --git a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/plugin.xml index b8a6762..a4c2994 100644 --- a/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.hierarchyviewer/plugin.xml @@ -77,7 +77,7 @@ name="Pixel Perfect"/> <perspective class="com.android.ide.eclipse.hierarchyviewer.TreeViewPerspective" - icon="icons/tree-view.png" + icon="icons/hierarchyviewer-16.png" id="com.android.ide.eclipse.hierarchyviewer.TreeViewPerspective" name="Hierarchy View"/> </extension> diff --git a/eclipse/plugins/com.android.ide.eclipse.pdt/.settings/org.eclipse.jdt.core.prefs b/eclipse/plugins/com.android.ide.eclipse.pdt/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.pdt/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/eclipse/plugins/com.android.ide.eclipse.pdt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.pdt/META-INF/MANIFEST.MF index 15f9510..3781ddc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.pdt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.pdt/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Pdt Bundle-SymbolicName: com.android.ide.eclipse.pdt;singleton:=true -Bundle-Version: 10.0.0.qualifier +Bundle-Version: 11.0.0.qualifier Bundle-Vendor: The Android Open Source Project Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/.settings/org.eclipse.jdt.core.prefs b/eclipse/plugins/com.android.ide.eclipse.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF index 9d4285c..b6fe951 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF @@ -2,9 +2,9 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Android Plugin Tests Bundle-SymbolicName: com.android.ide.eclipse.tests -Bundle-Version: 10.0.0.qualifier +Bundle-Version: 11.0.0.qualifier Bundle-Vendor: The Android Open Source Project -Fragment-Host: com.android.ide.eclipse.adt;bundle-version="10.0.0" +Fragment-Host: com.android.ide.eclipse.adt;bundle-version="11.0.0" Require-Bundle: org.junit Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ClassPath: kxml2-2.3.0.jar, diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java new file mode 100644 index 0000000..af4e2b7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptParserTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.build; + +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +public class AaptParserTest extends AdtProjectTest { + + public void testBasic() throws Exception { + // Test the "at 'property' with value 'value' range matching included with most aapt errors + checkRanges("quickfix1.xml", "res/layout/quickfix1.xml", + "quickfix1.xml:7: error: Error: No resource found that matches the given name (at" + + " 'text' with value '@string/firststring').", + "android:text=\"^@string/firststring\"", + "android:text=\"@string/firststring^\""); + } + + public void testRange1() throws Exception { + // Check that when the actual aapt error occurs on a line later than the original error + // line, the forward search which looks for a value match does not stop on an + // earlier line that happens to have the same value prefix + checkRanges("aapterror1.xml", "res/layout/aapterror1.xml", + "aapterror1.xml:5: error: Error: Integer types not allowed (at " + + "'layout_marginBottom' with value '50').", + "marginBottom=\"^50\"", "marginBottom=\"50^\""); + } + + public void testRange2() throws Exception { + // Check that when we have a duplicate resource error, we highlight both the original + // property and the original definition. + // This tests the second, duplicate declaration ration. + checkRanges("aapterror2.xml", "res/values/aapterror2.xml", + "aapterror2.xml:7: error: Resource entry repeatedStyle1 already has bag item " + + "android:gravity.", + "<item name=\"^android:gravity\">bottom</item>", + "<item name=\"android:gravity^\">bottom</item>"); + } + + public void testRange3() throws Exception { + // Check that when we have a duplicate resource error, we highlight both the original + // property and the original definition. + // This tests the original definition. Note that we don't have enough position info + // so we simply highlight the whitespace portion of the line. + checkRanges("aapterror2.xml", "res/values/aapterror2.xml", + "aapterror2.xml:4: Originally defined here.", + "^<item name=\"android:gravity\">left</item>", + "<item name=\"android:gravity\">left</item>^"); + } + + public void testRange4() throws Exception { + // Check for aapt error which occurs when the attribute name in an item style declaration + // is nonexistent + checkRanges("aapterror3.xml", "res/values/aapterror3.xml", + "aapterror3.xml:4: error: Error: No resource found that matches the given name: " + + "attr 'nonexistent'.", + "<item name=\"^nonexistent\">5</item>", + "<item name=\"nonexistent^\">5</item>"); + } + + public void testRange5() throws Exception { + // Test missing resource name + checkRanges("aapterror4.xml", "res/values/aapterror4.xml", + "aapterror4.xml:3: error: A 'name' attribute is required for <style>", + "<^style>", + "<style^>"); + } + + public void testRange6() throws Exception { + checkRanges("aapterror4.xml", "res/values/aapterror4.xml", + "aapterror4.xml:6: error: A 'type' attribute is required for <item>", + "<^item></item>", + "<item^></item>"); + } + + public void testRange7() throws Exception { + // Test missing resource name + checkRanges("aapterror4.xml", "res/values/aapterror4.xml", + "aapterror4.xml:6: error: A 'name' attribute is required for <item>", + "<^item></item>", + "<item^></item>"); + } + + // This test is disabled because I can't find a useful scenario for handling this error + // message. When this error occurs, we will also get a warning on a missing attribute, and + // that warning already underlines the element name. + //public void testRange8() throws Exception { + // // Test missing resource name + // checkRanges("aapterror4.xml", "res/values/aapterror4.xml", + // "aapterror4.xml:4: error: Error: Resource id cannot be an empty string: attr ''.", + // " ^<item />", + // " <item />^"); + //} + + public void testRange9() throws Exception { + // Test missing resource name + checkRanges("aapterror5.xml", "res/values/aapterror5.xml", + "aapterror5.xml:4: error: Error: String types not allowed (at " + + "'android:layout_width' with value '').", + " <item name=\"^android:layout_width\"></item>", + " <item name=\"android:layout_width^\"></item>"); + } + + public void testRange10() throws Exception { + // Test missing resource name + checkRanges("aapterror6.xml", "res/layout/aapterror6.xml", + "aapterror6.xml:5: error: Error: String types not allowed (at 'layout_marginTop'" + + " with value '').", + "android:layout_marginTop=^\"\"", + "android:layout_marginTop=\"\"^"); + } + + public void testRange11() throws Exception { + // Test missing resource name + checkRanges("aapterror6.xml", "res/layout/aapterror6.xml", + "aapterror1.xml:5: error: Error: String types not allowed (at 'layout_marginLeft'" + + " with value '').", + "android:layout_marginLeft=^''", + "android:layout_marginLeft=''^"); + } + + public void testRange12() throws Exception { + // Test missing resource name + checkRanges("aapterror7.xml", "res/layout/aapterror7.xml", + "aapterror7.xml:5: error: Error: String types not allowed (at 'id'" + + " with value '').", + "android:id=^\"\"", + "android:id=\"\"^"); + } + + private void checkRanges(String name, String destPath, String aaptError, + String expectCaretBegin, String expectCaretEnd) + throws Exception { + IProject project = getProject(); + IFile file = getTestDataFile(project, name, destPath); + + // Make file paths absolute + String osRoot = project.getLocation().toOSString(); + String fileRelativePath = file.getProjectRelativePath().toPortableString(); + String filePath = osRoot + File.separator + fileRelativePath; + String originalError = filePath + aaptError.substring(aaptError.indexOf(':')); + List<String> errors = Collections.singletonList(originalError); + + // Remove anything already placed there by the project create/build automatic + // (this usually only happens while debugging so the background thread has a chance + // to get things going) + IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true, + IResource.DEPTH_ZERO); + for (IMarker marker : markers) { + marker.delete(); + } + + AaptParser.parseOutput(errors, project); + markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true, + IResource.DEPTH_ZERO); + assertNotNull(markers); + assertEquals(1, markers.length); + + String fileContents = AdtPlugin.readFile(file); + int rangeBegin = getCaretOffset(file, expectCaretBegin); + int rangeEnd = getCaretOffset(file, expectCaretEnd); + + // Check text range + IMarker marker = markers[0]; + String message = marker.getAttribute(IMarker.MESSAGE, ""); //$NON-NLS-1$ + String simplerMessage = aaptError.substring(aaptError.indexOf(' ') + 1); + assertEquals(simplerMessage, message); + int start = marker.getAttribute(IMarker.CHAR_START, 0); + int end = marker.getAttribute(IMarker.CHAR_END, 0); + + assertEquals("Wrong start offset, expected " + expectCaretBegin + " but was " + + getCaretContext(fileContents, start), rangeBegin, start); + assertEquals("Wrong end offset, expected " + expectCaretEnd + " but was " + + getCaretContext(fileContents, end), rangeEnd, end); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java new file mode 100644 index 0000000..78f16a2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFixTest.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.build; + +import static com.android.AndroidConstants.FD_RES_COLOR; +import static com.android.AndroidConstants.FD_RES_LAYOUT; +import static com.android.sdklib.SdkConstants.FD_RES; + +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; +import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.FileEditorInput; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class AaptQuickFixTest extends AdtProjectTest { + @Override + protected boolean testCaseNeedsUniqueProject() { + // Make a separate test project for this test such that we don't pollute code assist + // tests with our new resources + return true; + } + + public void testQuickFix1() throws Exception { + // Test adding a value into an existing file (res/values/strings.xml) + checkResourceFix("quickfix1.xml", "android:text=\"@string/firs^tstring\"", + "res/values/strings.xml"); + } + + public void testQuickFix2() throws Exception { + // Test adding a value into a new file (res/values/dimens.xml, will be created) + checkResourceFix("quickfix1.xml", "android:layout_width=\"@dimen/^testdimen\"", + "res/values/dimens.xml"); + } + + public void testQuickFix3() throws Exception { + // Test adding a file based resource (uses new file wizard machinery) + checkResourceFix("quickfix1.xml", "layout=\"@layout/^testlayout\"", + "res/layout/testlayout.xml"); + } + + public void testQuickFix4() throws Exception { + // Test adding a value into a new file (res/values/dimens.xml, will be created) + checkNamespaceFix("quickfix2.xml", "<c^olor"); + } + + private void checkResourceFix(String name, String caretLocation, String expectedNewPath) + throws Exception { + IProject project = getProject(); + IFile file = getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name); + + // Determine the offset + final int offset = getCaretOffset(file, caretLocation); + + + String osRoot = project.getLocation().toOSString(); + List<String> errors = new ArrayList<String>(); + String fileRelativePath = file.getProjectRelativePath().toPortableString(); + String filePath = osRoot + File.separator + fileRelativePath; + // Run AaptParser such that markers are added... + // When debugging these tests, the project gets a chance to build itself so + // the real aapt errors are there. But when the test is run directly, aapt has + // not yet run. I tried waiting for the build (using the code in SampleProjectTest) + // but this had various adverse effects (exception popups from the Eclipse debugger + // etc) so instead this test just hardcodes the aapt errors that should be + // observed on quickfix1.xml. + assertEquals("Unit test is hardcoded to errors for quickfix1.xml", "quickfix1.xml", name); + errors.add(filePath + ":7: error: Error: No resource found that matches the given name" + + " (at 'text' with value '@string/firststring')."); + errors.add(filePath + ":7: error: Error: No resource found that matches the given name" + + " (at 'layout_width' with value '@dimen/testdimen')."); + errors.add(filePath + ":13: error: Error: No resource found that matches the given name" + + " (at 'layout' with value '@layout/testlayout')."); + AaptParser.parseOutput(errors, project); + + AaptQuickFix aaptQuickFix = new AaptQuickFix(); + + // Open file + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + assertNotNull(page); + IEditorPart editor = IDE.openEditor(page, file); + assertTrue(editor instanceof AndroidXmlEditor); + AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor; + final ISourceViewer viewer = layoutEditor.getStructuredSourceViewer(); + + // Test marker resolution. + IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true, + IResource.DEPTH_ZERO); + for (IMarker marker : markers) { + int start = marker.getAttribute(IMarker.CHAR_START, 0); + int end = marker.getAttribute(IMarker.CHAR_END, 0); + if (offset >= start && offset <= end) { + // Found the target marker. Now check the marker resolution of it. + assertTrue(aaptQuickFix.hasResolutions(marker)); + IMarkerResolution[] resolutions = aaptQuickFix.getResolutions(marker); + assertNotNull(resolutions); + assertEquals(1, resolutions.length); + IMarkerResolution resolution = resolutions[0]; + assertNotNull(resolution); + assertTrue(resolution.getLabel().contains("Create resource")); + + // Not running marker yet -- if we create the files here they already + // exist when the quick assist code runs. (The quick fix and the quick assist + // mostly share code for the implementation anyway.) + //resolution.run(marker); + break; + } + } + + // Next test quick assist. + + IQuickAssistInvocationContext invocationContext = new IQuickAssistInvocationContext() { + public int getLength() { + return 0; + } + + public int getOffset() { + return offset; + } + + public ISourceViewer getSourceViewer() { + return viewer; + } + }; + ICompletionProposal[] proposals = aaptQuickFix + .computeQuickAssistProposals(invocationContext); + assertNotNull(proposals); + assertTrue(proposals.length == 1); + ICompletionProposal proposal = proposals[0]; + + assertNotNull(proposal.getAdditionalProposalInfo()); + assertNotNull(proposal.getImage()); + assertTrue(proposal.getDisplayString().contains("Create resource")); + + IDocument document = new Document(); + String fileContent = AdtPlugin.readFile(file); + document.set(fileContent); + + // Apply quick fix + proposal.apply(document); + + IPath path = new Path(expectedNewPath); + IFile newFile = project.getFile(path); + assertNotNull(path.toPortableString(), newFile); + + // Ensure that the newly created file was opened + IEditorPart currentFile = Hyperlinks.getEditor(); + assertEquals(newFile.getProjectRelativePath(), + ((FileEditorInput) currentFile.getEditorInput()).getFile().getProjectRelativePath()); + + // Look up caret offset + assertTrue(currentFile != null ? currentFile.getClass().getName() : "null", + currentFile instanceof AndroidXmlEditor); + AndroidXmlEditor newEditor = (AndroidXmlEditor) currentFile; + ISourceViewer newViewer = newEditor.getStructuredSourceViewer(); + Point selectedRange = newViewer.getSelectedRange(); + + String newFileContents = AdtPlugin.readFile(newFile); + + // Insert selection markers -- [ ] for the selection range, ^ for the caret + String newFileWithCaret = addSelection(newFileContents, selectedRange); + newFileWithCaret = removeSessionData(newFileWithCaret); + + assertEqualsGolden(name, newFileWithCaret); + } + + private void checkNamespaceFix(String name, String caretLocation) + throws Exception { + IProject project = getProject(); + IFile file = getTestDataFile(project, name, FD_RES + "/" + FD_RES_COLOR + "/" + name); + + // Determine the offset + final int offset = getCaretOffset(file, caretLocation); + + String osRoot = project.getLocation().toOSString(); + List<String> errors = new ArrayList<String>(); + String fileRelativePath = file.getProjectRelativePath().toPortableString(); + String filePath = osRoot + File.separator + fileRelativePath; + assertEquals("Unit test is hardcoded to errors for quickfix2.xml", "quickfix2.xml", name); + errors.add(filePath + ":5: error: Error parsing XML: unbound prefix"); + AaptParser.parseOutput(errors, project); + + AaptQuickFix aaptQuickFix = new AaptQuickFix(); + + // Open file + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + assertNotNull(page); + IEditorPart editor = IDE.openEditor(page, file); + assertTrue(editor instanceof AndroidXmlEditor); + AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor; + final ISourceViewer viewer = layoutEditor.getStructuredSourceViewer(); + + // Test marker resolution. + IMarker[] markers = file.findMarkers(AdtConstants.MARKER_AAPT_COMPILE, true, + IResource.DEPTH_ZERO); + assertEquals(1, markers.length); + IMarker marker = markers[0]; + // Found the target marker. Now check the marker resolution of it. + assertTrue(aaptQuickFix.hasResolutions(marker)); + IMarkerResolution[] resolutions = aaptQuickFix.getResolutions(marker); + assertNotNull(resolutions); + assertEquals(1, resolutions.length); + IMarkerResolution resolution = resolutions[0]; + assertNotNull(resolution); + assertTrue(resolution.getLabel().contains("Insert namespace")); + + // Next test quick assist. + + IQuickAssistInvocationContext invocationContext = new IQuickAssistInvocationContext() { + public int getLength() { + return 0; + } + + public int getOffset() { + return offset; + } + + public ISourceViewer getSourceViewer() { + return viewer; + } + }; + ICompletionProposal[] proposals = aaptQuickFix + .computeQuickAssistProposals(invocationContext); + assertNotNull(proposals); + assertTrue(proposals.length == 1); + ICompletionProposal proposal = proposals[0]; + + assertNotNull(proposal.getAdditionalProposalInfo()); + assertNotNull(proposal.getImage()); + assertTrue(proposal.getDisplayString().contains("Insert namespace")); + + // Open the file to ensure we can get an XML model with getExistingModelForEdit: + AdtPlugin.openFile(file, null); + IEditorPart newEditor = Hyperlinks.getEditor(); + assertTrue(newEditor instanceof AndroidXmlEditor); + + AndroidXmlEditor xmlEditor = (AndroidXmlEditor) newEditor; + IDocument document = xmlEditor.getStructuredSourceViewer().getDocument(); + + // Apply quick fix + String before = document.get(); + proposal.apply(document); + String after = document.get(); + String diff = getDiff(before, after); + assertEqualsGolden(name, diff); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java new file mode 100644 index 0000000..c7a452e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java @@ -0,0 +1,836 @@ +/* + + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors; + +import static com.android.AndroidConstants.FD_RES_ANIM; +import static com.android.AndroidConstants.FD_RES_ANIMATOR; +import static com.android.AndroidConstants.FD_RES_COLOR; +import static com.android.AndroidConstants.FD_RES_DRAWABLE; +import static com.android.sdklib.SdkConstants.FD_RES; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.animator.AnimationContentAssist; +import com.android.ide.eclipse.adt.internal.editors.color.ColorContentAssist; +import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableContentAssist; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutContentAssist; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; +import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestContentAssist; +import com.android.ide.eclipse.adt.internal.editors.resources.ResourcesContentAssist; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; + +public class AndroidContentAssistTest extends AdtProjectTest { + private static final String CARET = "^"; //$NON-NLS-1$ + + public void testStartsWith() { + assertTrue(AndroidContentAssist.startsWith("", "")); + assertTrue(AndroidContentAssist.startsWith("a", "")); + assertTrue(AndroidContentAssist.startsWith("A", "")); + assertTrue(AndroidContentAssist.startsWith("A", "a")); + assertTrue(AndroidContentAssist.startsWith("A", "A")); + assertTrue(AndroidContentAssist.startsWith("Ab", "a")); + assertTrue(AndroidContentAssist.startsWith("ab", "A")); + assertTrue(AndroidContentAssist.startsWith("ab", "AB")); + assertFalse(AndroidContentAssist.startsWith("ab", "ABc")); + assertFalse(AndroidContentAssist.startsWith("", "ABc")); + } + + public void testNameStartsWith() { + String fullWord = "android:marginTop"; + for (int i = 0; i < fullWord.length(); i++) { + assertTrue(i + ":" + fullWord.substring(0, i), + AndroidContentAssist.nameStartsWith( + "android:layout_marginTop", fullWord.substring(0, i), "android:")); + } + + fullWord = "android:layout_marginTop"; + for (int i = 0; i < fullWord.length(); i++) { + assertTrue(i + ":" + fullWord.substring(0, i), + AndroidContentAssist.nameStartsWith("android:layout_marginTop", fullWord + .substring(0, i), "android:")); + } + + fullWord = "layout_marginTop"; + for (int i = 0; i < fullWord.length(); i++) { + assertTrue(i + ":" + fullWord.substring(0, i), + AndroidContentAssist.nameStartsWith("android:layout_marginTop", fullWord + .substring(0, i), "android:")); + } + + fullWord = "marginTop"; + for (int i = 0; i < fullWord.length(); i++) { + assertTrue(i + ":" + fullWord.substring(0, i), + AndroidContentAssist.nameStartsWith("android:layout_marginTop", fullWord + .substring(0, i), "android:")); + } + + assertFalse(AndroidContentAssist.nameStartsWith("ab", "ABc", null)); + assertFalse(AndroidContentAssist.nameStartsWith("", "ABc", null)); + } + + public void testCompletion1() throws Exception { + // Change attribute name completion + checkLayoutCompletion("completion1.xml", "layout_w^idth=\"fill_parent\""); + } + + public void testCompletion2() throws Exception { + // Check attribute value completion for enum + checkLayoutCompletion("completion1.xml", "layout_width=\"^fill_parent\""); + } + + public void testCompletion3() throws Exception { + // Check attribute value completion for enum with a prefix + checkLayoutCompletion("completion1.xml", "layout_width=\"fi^ll_parent\""); + } + + public void testCompletion4() throws Exception { + // Check attribute value completion on units + checkLayoutCompletion("completion1.xml", "marginBottom=\"50^\""); + } + + public void testCompletion5() throws Exception { + // Check attribute value completion on units with prefix + checkLayoutCompletion("completion1.xml", "layout_marginLeft=\"50d^p\""); + } + + public void testCompletion6() throws Exception { + // Check resource sorting - "style" should bubble to the top for a style attribute + checkLayoutCompletion("completion1.xml", "style=\"@android:^style/Widget.Button\""); + } + + public void testCompletion7a() throws Exception { + // Check flags (multiple values inside a single XML value, separated by | - where + // the prefix is reset as soon as you pass each | ) + checkLayoutCompletion("completion1.xml", "android:gravity=\"l^eft|bottom\""); + } + + public void testCompletion7b() throws Exception { + checkLayoutCompletion("completion1.xml", "android:gravity=\"left|b^ottom\""); + } + + public void testCompletion8() throws Exception { + // Test completion right at the "=" sign; this will be taken to be the last + // character of the attribute name (the caret is between the last char and before + // the = characters), so it should match a single attribute + checkLayoutCompletion("completion1.xml", "layout_width^=\"fill_parent\""); + } + + public void testCompletion9() throws Exception { + // Test completion right after the "=" sign; this will be taken to be the beginning + // of the attribute value, but all values will also include a leading quote + checkLayoutCompletion("completion1.xml", "layout_width=^\"fill_parent\""); + } + + public void testCompletion10() throws Exception { + // Test completion of element names + checkLayoutCompletion("completion1.xml", "<T^extView"); + } + + public void testCompletion11() throws Exception { + // Test completion of element names at the outside of the <. This should include + // all the elements too (along with the leading <). + checkLayoutCompletion("completion1.xml", "^<TextView"); + } + + public void testCompletion12() throws Exception { + // Test completion of element names inside a nested XML; ensure that this will + // correctly compute element names, not previous attribute + checkLayoutCompletion("completion1.xml", "btn_default\">^</FrameLayout>"); + } + + public void testCompletion13a() throws Exception { + checkLayoutCompletion("completion2.xml", "gravity=\"left|bottom|^cen"); + } + + public void testCompletion13b() throws Exception { + checkLayoutCompletion("completion2.xml", "gravity=\"left|bottom|cen^"); + } + + public void testCompletion13c() throws Exception { + checkLayoutCompletion("completion2.xml", "gravity=\"left|bottom^|cen"); + } + + public void testCompletion14() throws Exception { + // Test completion of permissions + checkManifestCompletion("manifest.xml", "android.permission.ACC^ESS_NETWORK_STATE"); + } + + public void testCompletion15() throws Exception { + // Test completion of intents + checkManifestCompletion("manifest.xml", "android.intent.category.L^AUNCHER"); + } + + public void testCompletion16() throws Exception { + // Test completion of top level elements + checkManifestCompletion("manifest.xml", "<^application android:i"); + } + + public void testCompletion17() throws Exception { + // Test completion of attributes on the manifest element + checkManifestCompletion("manifest.xml", "^android:versionCode=\"1\""); + } + + public void testCompletion18() throws Exception { + // Test completion of attributes on the manifest element + checkManifestCompletion("manifest.xml", + "<activity android:^name=\".TestActivity\""); + } + + public void testCompletion19() throws Exception { + // Test special case where completing on a new element in an otherwise blank line + // does not add in full completion (with closing tags) + checkLayoutCompletion("broken3.xml", "<EditT^"); + } + + public void testCompletion20() throws Exception { + checkLayoutCompletion("broken1.xml", "android:textColorHigh^"); + } + + public void testCompletion21() throws Exception { + checkLayoutCompletion("broken2.xml", "style=^"); + } + + public void testCompletion22() throws Exception { + // Test completion where the cursor is inside an element (e.g. the next + // char is NOT a <) - should not complete with end tags + checkLayoutCompletion("completion4.xml", "<Button^"); + } + + // Test completion in style files + + public void testCompletion23() throws Exception { + checkResourceCompletion("completionvalues1.xml", "android:textS^ize"); + } + + public void testCompletion24() throws Exception { + checkResourceCompletion("completionvalues1.xml", "17^sp"); + } + + public void testCompletion25() throws Exception { + checkResourceCompletion("completionvalues1.xml", "textColor\">^@color/title_color</item>"); + } + + public void testCompletion26() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:shadowColor\">@an^</item>"); + } + + public void testCompletion27() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:gravity\">^ </item>"); + } + + public void testCompletion28() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:gravity\"> ^</item>"); + } + + public void testCompletion29() throws Exception { + checkResourceCompletion("completionvalues1.xml", "<item name=\"gr^\">"); + } + + public void testCompletion30() throws Exception { + checkResourceCompletion("completionvalues1.xml", "<item name=\"an^\">"); + } + + public void testCompletion31() throws Exception { + checkResourceCompletion("completionvalues1.xml", "<item ^></item>"); + } + + public void testCompletion32() throws Exception { + checkResourceCompletion("completionvalues1.xml", "<item name=\"^\"></item>"); + } + + public void testCompletion33() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:allowSingleTap\">^</item>"); + } + + public void testCompletion34() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:alwaysDrawnWithCache\">^ false </item>"); + } + + public void testCompletion35() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:alwaysDrawnWithCache\"> ^false </item>"); + } + + public void testCompletion36() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:alwaysDrawnWithCache\"> f^alse </item>"); + } + + public void testCompletion37() throws Exception { + checkResourceCompletion("completionvalues1.xml", + "<item name=\"android:orientation\">h^</item>"); + } + + public void testCompletion38() throws Exception { + checkResourceCompletion("completionvalues1.xml", + " c^"); + } + + public void testCompletion39() throws Exception { + // If you are at the end of a closing quote (but with no space), completion should + // include a separating space. + checkLayoutCompletion("completion1.xml", "marginBottom=\"50\"^"); + } + + public void testCompletion40() throws Exception { + // Same as test 39 but with single quote + checkLayoutCompletion("completion5.xml", "android:id='@+id/button2'^"); + } + + public void testCompletion41() throws Exception { + // Test prefix matching on layout_ with namespace prefix + checkLayoutCompletion("completion8.xml", "android:mar^=\"50dp\""); + } + + public void testCompletion42() throws Exception { + // Test prefix matching on layout_ with namespace prefix + checkLayoutCompletion("completion8.xml", "android:w^i=\"100\""); + } + + public void testCompletion43() throws Exception { + // Test prefix matching on layout_ without namespace prefix + checkLayoutCompletion("completion8.xml", "mar^=\"60dp\""); + } + + public void testCompletion44() throws Exception { + // Test prefix matching on layout_ without namespace prefix + checkLayoutCompletion("completion8.xml", "android:layo^ut_width=\"fill_parent\""); + } + + public void testCompletion45() throws Exception { + // Test top level elements in colors + checkColorCompletion("color1.xml", "^<selector"); + } + + public void testCompletion46a() throws Exception { + // Test children of selector: should offer item + checkColorCompletion("color1.xml", "^<item android"); + } + + public void testCompletion46b() throws Exception { + // Test attribute matching in color files + checkColorCompletion("color1.xml", "<item ^android:state_focused=\"true\"/>"); + } + + public void testCompletion47() throws Exception { + // Check root completion in drawables: should list all drawable root elements + checkDrawableCompletion("drawable1.xml", "^<layer-list"); + } + + public void testCompletion48() throws Exception { + // Check attributes of the layer list + checkDrawableCompletion("drawable1.xml", "^xmlns:android"); + } + + public void testCompletion49() throws Exception { + // Check attributes of the <item> element inside a <layer-list> + checkDrawableCompletion("drawable1.xml", "<item ^></item>"); + } + + public void testCompletion50() throws Exception { + // Check elements nested inside the <item> in a layer list: can use any drawable again + checkDrawableCompletion("drawable1.xml", "<item >^</item>"); + } + + public void testCompletion51() throws Exception { + // Check attributes of <shape> element + checkDrawableCompletion("drawable2.xml", "^android:innerRadiusRatio=\"2\""); + } + + public void testCompletion52() throws Exception { + // Check list of available elements inside a shape + checkDrawableCompletion("drawable2.xml", "^<gradient"); + } + + public void testCompletion53() throws Exception { + // Check list of root anim elements + checkAnimCompletion("anim1.xml", "^<set xmlns"); + } + + public void testCompletion54() throws Exception { + // Check that we can nest inside <set>'s + checkAnimCompletion("anim1.xml", "^<translate android:id="); + } + + public void testCompletion55() throws Exception { + // translate properties + checkAnimCompletion("anim1.xml", "android:^fromXDelta="); + } + + public void testCompletion56() throws Exception { + // alpha properties + checkAnimCompletion("anim1.xml", "android:^fromAlpha="); + } + + public void testCompletion57() throws Exception { + // Fractional properties + checkAnimCompletion("anim1.xml", "android:fromXDelta=\"100^%p\""); + } + + public void testCompletion58() throws Exception { + // Top level animator elements + checkAnimatorCompletion("animator1.xml", "^<set xmlns"); + } + + public void testCompletion59() throws Exception { + // objectAnimator properties + checkAnimatorCompletion("animator1.xml", "android:^duration=\"2000\""); + } + + public void testCompletion60() throws Exception { + // propertyName completion + checkAnimatorCompletion("animator1.xml", "android:propertyName=\"scal^eX\"/>"); + } + + public void testCompletion61() throws Exception { + // Interpolator completion + checkAnimatorCompletion("animator1.xml", + "android:interpolator=\"^@android:anim/bounce_interpolator\""); + } + + // ---- Test *applying* code completion ---- + + + + // The following tests check -applying- a specific code completion + // match - this verifies that the document is updated correctly, the + // caret is moved appropriately, etc. + + public void testApplyCompletion1() throws Exception { + // Change attribute name completion + checkApplyLayoutCompletion("completion1.xml", "layout_w^idth=\"fill_parent\"", + "android:layout_weight"); + } + + public void testApplyCompletion2() throws Exception { + // Check attribute value completion for enum + checkApplyLayoutCompletion("completion1.xml", "layout_width=\"^fill_parent\"", + "match_parent"); + } + + public void testApplyCompletion3() throws Exception { + // Check attribute value completion for enum with a prefix + checkApplyLayoutCompletion("completion1.xml", "layout_width=\"fi^ll_parent\"", + "fill_parent"); + } + + public void testApplyCompletion4() throws Exception { + // Check attribute value completion on units + checkApplyLayoutCompletion("completion1.xml", "marginBottom=\"50^\"", "50mm"); + } + + public void testApplyCompletion5() throws Exception { + // Check attribute value completion on units with prefix + checkApplyLayoutCompletion("completion1.xml", "layout_marginLeft=\"50d^p\"", "50dp"); + } + + public void testApplyCompletion6() throws Exception { + // Check resource sorting - "style" should bubble to the top for a style attribute + checkApplyLayoutCompletion("completion1.xml", "style=\"@android:^style/Widget.Button\"", + "@android:drawable/"); + } + + public void testApplyCompletion7a() throws Exception { + // Check flags (multiple values inside a single XML value, separated by | - where + // the prefix is reset as soon as you pass each | ) + checkApplyLayoutCompletion("completion1.xml", "android:gravity=\"l^eft|bottom\"", + "left"); + // NOTE - this will replace all flag values with the newly selected value. + // That may not be the best behavior - perhaps we should only replace one portion + // of the value. + } + + public void testApplyCompletion7b() throws Exception { + checkApplyLayoutCompletion("completion1.xml", "android:gravity=\"left|b^ottom\"", + "bottom"); + // NOTE - this will replace all flag values with the newly selected value. + // That may not be the best behavior - perhaps we should only replace one portion + // of the value. + } + + public void testApplyCompletion8() throws Exception { + // Test completion right at the "=" sign; this will be taken to be the last + // character of the attribute name (the caret is between the last char and before + // the = characters), so it should match a single attribute + checkApplyLayoutCompletion("completion1.xml", "layout_width^=\"fill_parent\"", + "android:layout_width"); + } + + public void testApplyCompletion9() throws Exception { + // Test completion right after the "=" sign; this will be taken to be the beginning + // of the attribute value, but all values will also include a leading quote + checkApplyLayoutCompletion("completion1.xml", "layout_width=^\"fill_parent\"", + "\"wrap_content\""); + } + + public void testApplyCompletion10() throws Exception { + // Test completion of element names + checkApplyLayoutCompletion("completion1.xml", "<T^extView", "TableLayout"); + } + + public void testApplyCompletion11a() throws Exception { + // Test completion of element names at the outside of the <. This should include + // all the elements too (along with the leading <). + checkApplyLayoutCompletion("completion1.xml", "^<TextView", "<RadioGroup ></RadioGroup>"); + } + + public void testApplyCompletion11b() throws Exception { + // Similar to testApplyCompletion11a, but replacing with an element that does not + // have children (to test the closing tag insertion code) + checkApplyLayoutCompletion("completion1.xml", "^<TextView", "<CheckBox />"); + } + + public void testApplyCompletion12() throws Exception { + // Test completion of element names inside a nested XML; ensure that this will + // correctly compute element names, not previous attribute + checkApplyLayoutCompletion("completion1.xml", "btn_default\">^</FrameLayout>", + "<FrameLayout ></FrameLayout>"); + } + + public void testApplyCompletion13a() throws Exception { + checkApplyLayoutCompletion("completion2.xml", "gravity=\"left|bottom|^cen", + "fill_vertical"); + } + + public void testApplyCompletion13b() throws Exception { + checkApplyLayoutCompletion("completion2.xml", "gravity=\"left|bottom|cen^", + "center_horizontal"); + } + + public void testApplyCompletion13c() throws Exception { + checkApplyLayoutCompletion("completion2.xml", "gravity=\"left|bottom^|cen", + "bottom|fill_horizontal"); + } + + public void testApplyCompletion14() throws Exception { + // Test special case where completing on a new element in an otherwise blank line + // does not add in full completion (with closing tags) + checkApplyLayoutCompletion("broken3.xml", "<EditT^", "EditText />"); + } + + public void testApplyCompletion15() throws Exception { + checkApplyLayoutCompletion("broken1.xml", "android:textColorHigh^", + "android:textColorHighlight"); + } + + public void testApplyCompletion16() throws Exception { + checkApplyLayoutCompletion("broken2.xml", "style=^", + "\"@android:\""); + } + + public void testApplyCompletion17() throws Exception { + // Make sure that completion right before a / inside an element still + // inserts the ="" part (e.g. handles it as "insertNew) + checkApplyLayoutCompletion("completion3.xml", "<EditText ^/>", + "android:textColorHighlight"); + } + + public void testApplyCompletion18() throws Exception { + // Make sure that completion right before a > inside an element still + // inserts the ="" part (e.g. handles it as "insertNew) + checkApplyLayoutCompletion("completion3.xml", "<Button ^></Button>", + "android:paddingRight"); + } + + public void testApplyCompletion19() throws Exception { + // Test completion with single quotes (apostrophe) + checkApplyLayoutCompletion("completion5.xml", "android:orientation='^'", "horizontal"); + } + + public void testApplyCompletion20() throws Exception { + // Test completion with single quotes (apostrophe) + checkApplyLayoutCompletion("completion5.xml", "android:layout_marginTop='50^dp'", "50pt"); + } + + public void testApplyCompletion21() throws Exception { + // Test completion with single quotes (apostrophe) + checkApplyLayoutCompletion("completion5.xml", "android:layout_width='^wrap_content'", + "match_parent"); + // Still broken - but not a common case + //checkApplyLayoutCompletion("completion5.xml", "android:layout_width=^'wrap_content'", + // "\"match_parent\""); + } + + public void testApplyCompletion22() throws Exception { + // Test completion in an empty string + checkApplyLayoutCompletion("completion6.xml", "android:orientation=\"^\"", "horizontal"); + } + + public void testApplyCompletion23() throws Exception { + // Test completion in an empty string + checkApplyLayoutCompletion("completion7.xml", "android:orientation=\"^", "horizontal"); + } + + // Test completion in style files + + public void testApplyCompletion24a() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", "android:textS^ize", + "android:textSelectHandleLeft"); + } + + public void testApplyCompletion24b() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", "17^sp", "17mm"); + } + + public void testApplyCompletion25() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "textColor\">^@color/title_color</item>", "@android:"); + } + + public void testApplyCompletion26() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:shadowColor\">@an^</item>", "@android:"); + } + + public void testApplyCompletion27() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:gravity\">^ </item>", "center_vertical"); + } + + public void testApplyCompletion28() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:gravity\"> ^</item>", "left"); + } + + public void testApplyCompletion29() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", "<item name=\"gr^\">", + "android:gravity"); + } + + public void testApplyCompletion30() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", "<item name=\"an^\">", + "android:animateOnClick"); + } + + public void testApplyCompletion31() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", "<item ^></item>", "name"); + } + + public void testApplyCompletion32() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", "<item name=\"^\"></item>", + "android:background"); + } + + public void testApplyCompletion33() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:allowSingleTap\">^</item>", "true"); + } + + public void testApplyCompletion34() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:alwaysDrawnWithCache\">^ false </item>", "true"); + } + + public void testApplyCompletion35() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:alwaysDrawnWithCache\"> ^false </item>", "true"); + } + + public void testApplyCompletion36() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:alwaysDrawnWithCache\"> f^alse </item>", "false"); + } + + public void testApplyCompletion37() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + "<item name=\"android:orientation\">h^</item>", "horizontal"); + } + + public void testApplyCompletion38() throws Exception { + checkApplyResourceCompletion("completionvalues1.xml", + " c^", "center"); + } + + public void testApplyCompletion39() throws Exception { + // If you are at the end of a closing quote (but with no space), completion should + // include a separating space. + checkApplyLayoutCompletion("completion1.xml", "marginBottom=\"50\"^", " android:maxEms"); + } + + public void testApplyCompletion40() throws Exception { + // If you are at the end of a closing quote (but with no space), completion should + // include a separating space. + checkApplyLayoutCompletion("completion5.xml", "android:id='@+id/button2'^", + " android:maxWidth"); + } + + public void testApplyCompletion41() throws Exception { + // Test prefix matching on layout_ with namespace prefix + checkApplyLayoutCompletion("completion8.xml", "android:mar^=\"50dp\"", + "android:layout_marginRight"); + } + + // --- Code Completion test infrastructure ---- + + private void checkLayoutCompletion(String name, String caretLocation) throws Exception { + checkCompletion(name, getLayoutFile(getProject(), name), caretLocation, + new LayoutContentAssist()); + } + + private void checkColorCompletion(String name, String caretLocation) throws Exception { + IFile file = getTestDataFile(getProject(), name, + FD_RES + "/" + FD_RES_COLOR + "/" + name); + checkCompletion(name, file, caretLocation, + new ColorContentAssist()); + } + private void checkAnimCompletion(String name, String caretLocation) throws Exception { + IFile file = getTestDataFile(getProject(), name, + FD_RES + "/" + FD_RES_ANIM + "/" + name); + checkCompletion(name, file, caretLocation, + new AnimationContentAssist()); + } + + private void checkAnimatorCompletion(String name, String caretLocation) throws Exception { + IFile file = getTestDataFile(getProject(), name, + FD_RES + "/" + FD_RES_ANIMATOR + "/" + name); + checkCompletion(name, file, caretLocation, + new AnimationContentAssist()); + } + + + private void checkDrawableCompletion(String name, String caretLocation) throws Exception { + IFile file = getTestDataFile(getProject(), name, + FD_RES + "/" + FD_RES_DRAWABLE + "/" + name); + checkCompletion(name, file, caretLocation, + new DrawableContentAssist()); + } + + private void checkManifestCompletion(String name, String caretLocation) throws Exception { + // Manifest files must be named AndroidManifest.xml. Must overwrite to replace + // the default manifest created in the test project. + IFile file = getTestDataFile(getProject(), name, "AndroidManifest.xml", true); + + checkCompletion(name, file, caretLocation, + new ManifestContentAssist()); + } + + private void checkApplyLayoutCompletion(String name, String caretLocation, + String match) throws Exception { + checkApplyCompletion(name, getLayoutFile(getProject(), name), caretLocation, + new LayoutContentAssist(), match); + } + + private void checkResourceCompletion(String name, String caretLocation) throws Exception { + checkCompletion(name, getValueFile(getProject(), name), caretLocation, + new ResourcesContentAssist()); + } + + private void checkApplyResourceCompletion(String name, String caretLocation, + String match) throws Exception { + checkApplyCompletion(name, getValueFile(getProject(), name), caretLocation, + new ResourcesContentAssist(), match); + } + + private ICompletionProposal[] complete(IFile file, String caretLocation, + AndroidContentAssist assist) throws Exception { + + // Determine the offset + int offset = getCaretOffset(file, caretLocation); + + // Open file + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + assertNotNull(page); + IEditorPart editor = IDE.openEditor(page, file); + assertTrue(editor instanceof AndroidXmlEditor); + AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor; + ISourceViewer viewer = layoutEditor.getStructuredSourceViewer(); + + // Run code completion + ICompletionProposal[] proposals = assist.computeCompletionProposals(viewer, offset); + if (proposals == null) { + proposals = new ICompletionProposal[0]; + } + + return proposals; + } + + private void checkApplyCompletion(String basename, IFile file, String caretLocation, + AndroidContentAssist assist, String match) throws Exception { + ICompletionProposal[] proposals = complete(file, caretLocation, assist); + ICompletionProposal chosen = null; + for (ICompletionProposal proposal : proposals) { + if (proposal.getDisplayString().equals(match)) { + chosen = proposal; + break; + } + } + assertNotNull(chosen); + assert chosen != null; // Eclipse null pointer analysis doesn't believe the JUnit assertion + + String fileContent = AdtPlugin.readFile(file); + IDocument document = new Document(); + document.set(fileContent); + + // Apply code completion + chosen.apply(document); + + // Insert caret location as well + Point location = chosen.getSelection(document); + document.replace(location.x, 0, CARET); + + String actual = document.get(); + + int offset = getCaretOffset(fileContent, caretLocation); + String beforeWithCaret = fileContent.substring(0, offset) + CARET + + fileContent.substring(offset); + + String diff = getDiff(beforeWithCaret, actual); + assertTrue(diff + " versus " + actual, diff.length() > 0 || beforeWithCaret.equals(actual)); + + StringBuilder summary = new StringBuilder(); + summary.append("Code completion in " + basename + " for " + caretLocation + " selecting " + match + ":\n"); + if (diff.length() == 0) { + diff = "No changes"; + } + summary.append(diff); + + //assertEqualsGolden(basename, actual); + assertEqualsGolden(basename, summary.toString(), "diff"); + } + + private void checkCompletion(String basename, IFile file, String caretLocation, + AndroidContentAssist assist) throws Exception { + ICompletionProposal[] proposals = complete(file, caretLocation, assist); + StringBuilder sb = new StringBuilder(1000); + sb.append("Code completion in " + basename + " for " + caretLocation + ":\n"); + for (ICompletionProposal proposal : proposals) { + // TODO: assertNotNull(proposal.getImage()); + sb.append(proposal.getDisplayString()); + String help = proposal.getAdditionalProposalInfo(); + if (help != null && help.trim().length() > 0) { + sb.append(" : "); + sb.append(help.replace('\n', ' ')); + } + sb.append('\n'); + } + assertEqualsGolden(basename, sb.toString(), "txt"); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadataTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadataTest.java new file mode 100644 index 0000000..7dc3f2d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadataTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; +import static com.android.ide.common.layout.LayoutConstants.ID_PREFIX; +import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; + +import com.android.ide.common.layout.BaseLayoutRule; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.w3c.dom.Node; + +@SuppressWarnings("restriction") // XML DOM model +public class LayoutMetadataTest extends AdtProjectTest { + public void testMetadata1() throws Exception { + Pair<IDocument, UiElementNode> pair = getNode("metadata.xml", "listView1"); + IDocument document = pair.getFirst(); + UiElementNode uiNode = pair.getSecond(); + Node node = uiNode.getXmlNode(); + + LayoutMetadata metadata = LayoutMetadata.get(); + assertNull(metadata.getProperty(document, node, "foo")); + String before = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\">\n" + + " </ListView>"; + assertEquals(before, getText(document, node)); + + // Set the property + metadata.setProperty(document, node, + "listitem", "@android:layout/simple_list_item_checked"); + String after = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\">\n" + + " <!-- Preview: listitem=@android:layout/simple_list_item_checked -->\n" + + " </ListView>"; + assertEquals(after, getText(document, node)); + + // Set a second property + metadata.setProperty(document, node, + "listheader", "@android:layout/browser_link_context_header"); + after = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\">\n" + + " <!-- Preview: \n" + + " listheader=@android:layout/browser_link_context_header\n" + + " listitem=@android:layout/simple_list_item_checked\n" + + " -->\n" + + " </ListView>"; + assertEquals(after, getText(document, node)); + + // Set list item to a different layout + metadata.setProperty(document, node, + "listitem", "@android:layout/simple_list_item_single_choice"); + after = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\">\n" + + " <!-- Preview: \n" + + " listheader=@android:layout/browser_link_context_header\n" + + " listitem=@android:layout/simple_list_item_single_choice\n" + + " -->\n" + + " </ListView>"; + assertEquals(after, getText(document, node)); + + // Set header to a different layout + metadata.setProperty(document, node, + "listheader", "@layout/foo"); + after = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\">\n" + + " <!-- Preview: \n" + + " listheader=@layout/foo\n" + + " listitem=@android:layout/simple_list_item_single_choice\n" + + " -->\n" + + " </ListView>"; + assertEquals(after, getText(document, node)); + + // Clear out list item + metadata.setProperty(document, node, + "listitem", null); + after = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\">\n" + + " <!-- Preview: listheader=@layout/foo -->\n" + + " </ListView>"; + assertEquals(after, getText(document, node)); + + // Clear out list header + metadata.setProperty(document, node, + "listheader", null); + after = + "<ListView android:layout_width=\"match_parent\" android:id=\"@+id/listView1\"\n" + + " android:layout_height=\"wrap_content\"></ListView>"; + assertEquals(after, getText(document, node)); + + // Check node expansion on the button which doesn't have an end tag: + before = "<Button android:text=\"Button\" android:id=\"@+id/button1\"/>"; + } + + public void testMetadata2() throws Exception { + Pair<IDocument, UiElementNode> pair = getNode("metadata.xml", "button1"); + IDocument document = pair.getFirst(); + UiElementNode uiNode = pair.getSecond(); + Node node = uiNode.getXmlNode(); + + LayoutMetadata metadata = LayoutMetadata.get(); + assertNull(metadata.getProperty(document, node, "foo")); + String before = + "<Button android:text=\"Button\" android:id=\"@+id/button1\"/>"; + assertEquals(before, getText(document, node)); + + // Set the property + metadata.setProperty(document, node, + "listitem", "@android:layout/simple_list_item_checked"); + String after = + "<Button android:text=\"Button\" android:id=\"@+id/button1\">\n" + + " <!-- Preview: listitem=@android:layout/simple_list_item_checked -->\n" + + " </Button>"; + assertEquals(after, getText(document, node)); + } + + // ==== Test utilities ==== + + private static String getText(IDocument document, Node node) throws Exception { + IndexedRegion region = (IndexedRegion) node; + // This often returns the wrong value: + //int length = region.getLength(); + int length = region.getEndOffset() - region.getStartOffset(); + return document.get(region.getStartOffset(), length); + } + + private Pair<IDocument, UiElementNode> getNode(String filename, String targetId) + throws Exception, PartInitException { + IFile file = getLayoutFile(getProject(), filename); + AdtPlugin.openFile(file, null); + IEditorPart newEditor = Hyperlinks.getEditor(); + assertTrue(newEditor instanceof AndroidXmlEditor); + AndroidXmlEditor xmlEditor = (AndroidXmlEditor) newEditor; + IStructuredDocument document = xmlEditor.getStructuredDocument(); + UiElementNode root = xmlEditor.getUiRootNode(); + assertNotNull(root); + UiElementNode node = findById(root, targetId); + assertNotNull(node); + Pair<IDocument, UiElementNode> pair = Pair.<IDocument, UiElementNode>of(document, node); + return pair; + } + + private static UiElementNode findById(UiElementNode node, String targetId) { + assertFalse(targetId.startsWith(NEW_ID_PREFIX)); + assertFalse(targetId.startsWith(ID_PREFIX)); + + String id = node.getAttributeValue(ATTR_ID); + if (id != null && targetId.equals(BaseLayoutRule.stripIdPrefix(id))) { + return node; + } + + for (UiElementNode child : node.getUiChildren()) { + UiElementNode result = findById(child, targetId); + if (result != null) { + return result; + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java new file mode 100644 index 0000000..3b83bd7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.AndroidConstants.FD_RES_LAYOUT; +import static com.android.AndroidConstants.FD_RES_VALUES; +import static com.android.sdklib.SdkConstants.FD_RES; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreationPage; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizard; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewTestProjectCreationPage; +import com.android.ide.eclipse.tests.SdkTestCase; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.IWizardContainer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkingSet; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("restriction") +public class AdtProjectTest extends SdkTestCase { + private static final int TARGET_API_LEVEL = 11; + /** Update golden files if different from the actual results */ + private static final boolean UPDATE_DIFFERENT_FILES = false; + /** Create golden files if missing */ + private static final boolean UPDATE_MISSING_FILES = true; + private static final String TEST_DATA_REL_PATH = + "eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/" + + "internal/editors/layout/refactoring/testdata"; + private static final String PROJECTNAME_PREFIX = "testproject-"; + private static final long TESTS_START_TIME = System.currentTimeMillis(); + private static File sTempDir = null; + + /** + * We don't stash the project used by each test case as a field such that test cases + * can share a single project instance (which is typically much faster). + * However, see {@link #getProjectName()} for exceptions to this sharing scheme. + */ + private static Map<String, IProject> sProjectMap = new HashMap<String, IProject>(); + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // Prevent preview icon computation during plugin test to make test faster + if (AdtPlugin.getDefault() == null) { + fail("This test must be run as an Eclipse plugin test, not a plain JUnit test!"); + } + AdtPrefs.getPrefs().setPaletteModes("ICON_TEXT"); //$NON-NLS-1$ + + getProject(); + } + + /** Set to true if the subclass test case should use a per-instance project rather + * than a shared project. This is needed by projects which modify the project in such + * a way that it affects what other tests see (for example, the quickfix resource creation + * tests will add in new resources, which the code completion tests will then list as + * possible matches if the code completion test is run after the quickfix test.) + * @return true to create a per-instance project instead of the default shared project + */ + protected boolean testCaseNeedsUniqueProject() { + return false; + } + + protected boolean testNeedsUniqueProject() { + return false; + } + + @Override + protected boolean validateSdk(IAndroidTarget target) { + // Not quite working yet. When enabled will make tests run faster. + //if (target.getVersion().getApiLevel() < TARGET_API_LEVEL) { + // return false; + //} + + return true; + } + + /** Returns a name to use for the project used in this test. Subclasses do not need to + * override this if they can share a project with others - which is the case if they do + * not modify the project in a way that does not affect other tests. For example + * the resource quickfix test will create new resources which affect what shows up + * in the code completion results, so the quickfix tests will override this method + * to produce a unique project for its own tests. + */ + private String getProjectName() { + if (testNeedsUniqueProject()) { + return PROJECTNAME_PREFIX + getClass().getSimpleName() + "-" + getName(); + } else if (testCaseNeedsUniqueProject()) { + return PROJECTNAME_PREFIX + getClass().getSimpleName(); + } else { + return PROJECTNAME_PREFIX + TESTS_START_TIME; + } + } + + protected IProject getProject() { + String projectName = getProjectName(); + IProject project = sProjectMap.get(projectName); + if (project == null) { + project = createProject(projectName); + assertNotNull(project); + sProjectMap.put(projectName, project); + } + + return project; + } + + protected IFile getTestDataFile(IProject project, String name) throws Exception { + return getTestDataFile(project, name, name); + } + + protected IFile getLayoutFile(IProject project, String name) throws Exception { + return getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name); + } + + protected IFile getValueFile(IProject project, String name) throws Exception { + return getTestDataFile(project, name, FD_RES + "/" + FD_RES_VALUES + "/" + name); + } + + protected IFile getTestDataFile(IProject project, String sourceName, + String destPath) throws Exception { + return getTestDataFile(project, sourceName, destPath, false); + } + + protected IFile getTestDataFile(IProject project, String sourceName, + String destPath, boolean overwrite) throws Exception { + String[] split = destPath.split("/"); //$NON-NLS-1$ + IContainer parent; + String name; + if (split.length == 1) { + parent = project; + name = destPath; + } else { + IFolder folder = project.getFolder(split[0]); + NullProgressMonitor monitor = new NullProgressMonitor(); + if (!folder.exists()) { + folder.create(true /* force */, true /* local */, monitor); + } + for (int i = 1, n = split.length; i < n -1; i++) { + IFolder subFolder = folder.getFolder(split[i]); + if (!subFolder.exists()) { + subFolder.create(true /* force */, true /* local */, monitor); + } + folder = subFolder; + } + name = split[split.length - 1]; + parent = folder; + } + IFile file = parent.getFile(new Path(name)); + if (overwrite && file.exists()) { + String currentContents = AdtPlugin.readFile(file); + String newContents = readTestFile(sourceName, true); + if (currentContents == null || !currentContents.equals(newContents)) { + file.delete(true, new NullProgressMonitor()); + } else { + return file; + } + } + if (!file.exists()) { + String xml = readTestFile(sourceName, true); + InputStream bstream = new ByteArrayInputStream(xml.getBytes("UTF-8")); //$NON-NLS-1$ + NullProgressMonitor monitor = new NullProgressMonitor(); + file.create(bstream, false /* force */, monitor); + } + + return file; + } + + protected IProject createProject(String name) { + IAndroidTarget target = null; + + IAndroidTarget[] targets = getSdk().getTargets(); + for (IAndroidTarget t : targets) { + if (t.getVersion().getApiLevel() >= TARGET_API_LEVEL) { + target = t; + break; + } + } + assertNotNull(target); + + final StubProjectWizard newProjCreator = new StubProjectWizard( + name, target); + newProjCreator.init(null, null); + // need to run finish on ui thread since it invokes a perspective switch + Display.getDefault().syncExec(new Runnable() { + public void run() { + newProjCreator.performFinish(); + } + }); + + return validateProjectExists(name); + } + + public void createTestProject() { + IAndroidTarget target = null; + + IAndroidTarget[] targets = getSdk().getTargets(); + for (IAndroidTarget t : targets) { + if (t.getVersion().getApiLevel() >= TARGET_API_LEVEL) { + target = t; + break; + } + } + assertNotNull(target); + } + + private static IProject validateProjectExists(String name) { + IProject iproject = getProject(name); + assertTrue(String.format("%s project not created", name), iproject.exists()); + assertTrue(String.format("%s project not opened", name), iproject.isOpen()); + return iproject; + } + + private static IProject getProject(String name) { + IProject iproject = ResourcesPlugin.getWorkspace().getRoot() + .getProject(name); + return iproject; + } + + protected int getCaretOffset(IFile file, String caretLocation) { + assertTrue(caretLocation, caretLocation.contains("^")); + + String fileContent = AdtPlugin.readFile(file); + return getCaretOffset(fileContent, caretLocation); + } + + protected int getCaretOffset(String fileContent, String caretLocation) { + assertTrue(caretLocation, caretLocation.contains("^")); + + int caretDelta = caretLocation.indexOf("^"); + assertTrue(caretLocation, caretDelta != -1); + String caretContext = caretLocation.substring(0, caretDelta) + + caretLocation.substring(caretDelta + 1); // +1: skip "^" + int caretContextIndex = fileContent.indexOf(caretContext); + assertTrue("Caret content " + caretContext + " not found in file", + caretContextIndex != -1); + return caretContextIndex + caretDelta; + } + + protected String addSelection(String newFileContents, Point selectedRange) { + int selectionBegin = selectedRange.x; + int selectionEnd = selectionBegin + selectedRange.y; + return addSelection(newFileContents, selectionBegin, selectionEnd); + } + + protected String addSelection(String newFileContents, int selectionBegin, int selectionEnd) { + // Insert selection markers -- [ ] for the selection range, ^ for the caret + String newFileWithCaret; + if (selectionBegin < selectionEnd) { + newFileWithCaret = newFileContents.substring(0, selectionBegin) + "[^" + + newFileContents.substring(selectionBegin, selectionEnd) + "]" + + newFileContents.substring(selectionEnd); + } else { + // Selected range + newFileWithCaret = newFileContents.substring(0, selectionBegin) + "^" + + newFileContents.substring(selectionBegin); + } + + return newFileWithCaret; + } + + protected String getCaretContext(String file, int offset) { + int windowSize = 20; + int begin = Math.max(0, offset - windowSize / 2); + int end = Math.min(file.length(), offset + windowSize / 2); + + return "..." + file.substring(begin, offset) + "^" + file.substring(offset, end) + "..."; + } + + /** + * Very primitive line differ, intended for files where there are very minor changes + * (such as code completion apply-tests) + */ + protected String getDiff(String before, String after) { + // Do line by line analysis + String[] beforeLines = before.split("\n"); + String[] afterLines = after.split("\n"); + + int firstDelta = 0; + for (; firstDelta < Math.min(beforeLines.length, afterLines.length); firstDelta++) { + if (!beforeLines[firstDelta].equals(afterLines[firstDelta])) { + break; + } + } + + if (firstDelta == beforeLines.length && firstDelta == afterLines.length) { + return ""; + } + + // Counts from the end of both arrays + int lastDelta = 0; + for (; lastDelta < Math.min(beforeLines.length, afterLines.length); lastDelta++) { + if (!beforeLines[beforeLines.length - 1 - lastDelta].equals( + afterLines[afterLines.length - 1 - lastDelta])) { + break; + } + } + + + boolean showBeforeWindow = firstDelta >= beforeLines.length - lastDelta; + boolean showAfterWindow = firstDelta >= afterLines.length - lastDelta; + + StringBuilder sb = new StringBuilder(); + if (showAfterWindow && firstDelta > 0) { + sb.append(" "); + sb.append(afterLines[firstDelta - 1]); + sb.append('\n'); + } + for (int i = firstDelta; i < beforeLines.length - lastDelta; i++) { + sb.append("< "); + sb.append(beforeLines[i]); + sb.append('\n'); + } + if (showAfterWindow && lastDelta < afterLines.length - 1) { + sb.append(" "); + sb.append(afterLines[afterLines.length - (lastDelta -1)]); + sb.append('\n'); + } + + sb.append("---\n"); + + if (showBeforeWindow && firstDelta > 0) { + sb.append(" "); + sb.append(beforeLines[firstDelta - 1]); + sb.append('\n'); + } + for (int i = firstDelta; i < afterLines.length - lastDelta; i++) { + sb.append("> "); + sb.append(afterLines[i]); + sb.append('\n'); + } + if (showBeforeWindow && lastDelta < beforeLines.length - 1) { + sb.append(" "); + sb.append(beforeLines[beforeLines.length - (lastDelta -1)]); + sb.append('\n'); + } + + return sb.toString(); + } + + protected String removeSessionData(String data) { + if (getProject() != null) { + data = data.replace(getProject().getName(), "PROJECTNAME"); + } + + return data; + } + + public static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) { + if (hasChildren) { + return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0], + new AttributeDescriptor[0], new ElementDescriptor[1], false); + } else { + return new ViewElementDescriptor(name, fqn); + } + } + + public static UiViewElementNode createNode(UiViewElementNode parent, String fqn, + boolean hasChildren) { + String name = fqn.substring(fqn.lastIndexOf('.') + 1); + ViewElementDescriptor descriptor = createDesc(name, fqn, hasChildren); + if (parent == null) { + // All node hierarchies should be wrapped inside a document node at the root + parent = new UiViewElementNode(createDesc("doc", "doc", true)); + } + return (UiViewElementNode) parent.appendNewUiChild(descriptor); + } + + public static UiViewElementNode createNode(String fqn, boolean hasChildren) { + return createNode(null, fqn, hasChildren); + } + + protected String readTestFile(String relativePath, boolean expectExists) { + String path = "testdata" + File.separator + relativePath; //$NON-NLS-1$ + InputStream stream = + AdtProjectTest.class.getResourceAsStream(path); + if (!expectExists && stream == null) { + return null; + } + + assertNotNull(relativePath + " does not exist", stream); + + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + String xml = AdtPlugin.readFile(reader); + assertNotNull(xml); + assertTrue(xml.length() > 0); + + // Remove any references to the project name such that we are isolated from + // that in golden file. + // Appears in strings.xml etc. + xml = removeSessionData(xml); + + return xml; + } + + protected void assertEqualsGolden(String basename, String actual) { + assertEqualsGolden(basename, actual, basename.substring(basename.lastIndexOf('.') + 1)); + } + + protected void assertEqualsGolden(String basename, String actual, String newExtension) { + String testName = getName(); + if (testName.startsWith("test")) { + testName = testName.substring(4); + if (Character.isUpperCase(testName.charAt(0))) { + testName = Character.toLowerCase(testName.charAt(0)) + testName.substring(1); + } + } + String expectedName; + String extension = basename.substring(basename.lastIndexOf('.') + 1); + if (newExtension == null) { + newExtension = extension; + } + expectedName = basename.substring(0, basename.indexOf('.')) + + "-expected-" + testName + '.' + newExtension; + String expected = readTestFile(expectedName, false); + if (expected == null) { + File expectedPath = new File( + UPDATE_MISSING_FILES ? getTargetDir() : getTempDir(), expectedName); + AdtPlugin.writeFile(expectedPath, actual); + System.out.println("Expected - written to " + expectedPath + ":\n"); + System.out.println(actual); + fail("Did not find golden file (" + expectedName + "): Wrote contents as " + + expectedPath); + } else { + if (!expected.equals(actual)) { + File expectedPath = new File(getTempDir(), expectedName); + File actualPath = new File(getTempDir(), + expectedName.replace("expected", "actual")); + AdtPlugin.writeFile(expectedPath, expected); + AdtPlugin.writeFile(actualPath, actual); + // Also update data dir with the current value + if (UPDATE_DIFFERENT_FILES) { + AdtPlugin.writeFile( new File(getTargetDir(), expectedName), actual); + } + System.out.println("The files differ: diff " + expectedPath + " " + + actualPath); + assertEquals("The files differ - see " + expectedPath + " versus " + actualPath, + expected, actual); + } + } + } + + /** Get the location to write missing golden files to */ + protected File getTargetDir() { + // Set $ADT_SDK_SOURCE_PATH to point to your git "sdk" directory; if done, then + // if you run a unit test which refers to a golden file which does not exist, it + // will be created directly into the test data directory and you can rerun the + // test + // and it should pass (after you verify that the golden file contains the correct + // result of course). + String sdk = System.getenv("ADT_SDK_SOURCE_PATH"); + if (sdk != null) { + File sdkPath = new File(sdk); + if (sdkPath.exists()) { + File testData = new File(sdkPath, TEST_DATA_REL_PATH.replace('/', + File.separatorChar)); + if (testData.exists()) { + return testData; + } + } + } + return getTempDir(); + } + + protected File getTempDir() { + if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) { + return new File("/tmp"); //$NON-NLS-1$ + } + + if (sTempDir == null) { + // On Windows, we don't want to pollute the temp folder (which is generally + // already incredibly busy). So let's create a temp folder for the results. + + File base = new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$ + + Calendar c = Calendar.getInstance(); + String name = String.format("adtTests_%1$tF_%1$tT", c).replace(':', '-'); //$NON-NLS-1$ + File tmpDir = new File(base, name); + if (!tmpDir.exists() && tmpDir.mkdir()) { + sTempDir = tmpDir; + } else { + sTempDir = base; + } + } + + return sTempDir; + } + + /** Special editor context set on the model to be rendered */ + protected static class TestLayoutEditor extends LayoutEditor { + private final IFile mFile; + private final IStructuredDocument mStructuredDocument; + private UiDocumentNode mUiRootNode; + + public TestLayoutEditor(IFile file, IStructuredDocument structuredDocument, + UiDocumentNode uiRootNode) { + mFile = file; + mStructuredDocument = structuredDocument; + mUiRootNode = uiRootNode; + } + + @Override + public IFile getInputFile() { + return mFile; + } + + @Override + public IProject getProject() { + return mFile.getProject(); + } + + @Override + public IStructuredDocument getStructuredDocument() { + return mStructuredDocument; + } + + @Override + public UiDocumentNode getUiRootNode() { + return mUiRootNode; + } + + @Override + public void editorDirtyStateChanged() { + } + + @Override + public IStructuredModel getModelForRead() { + IModelManager mm = StructuredModelManager.getModelManager(); + if (mm != null) { + try { + return mm.getModelForRead(mFile); + } catch (Exception e) { + fail(e.toString()); + } + } + + return null; + } + } + + /** + * Stub class for project creation wizard. + * <p/> + * Created so project creation logic can be run without UI creation/manipulation. + */ + public class StubProjectWizard extends NewProjectWizard { + + private final String mProjectName; + private final IAndroidTarget mTarget; + + public StubProjectWizard(String projectName, IAndroidTarget target) { + this.mProjectName = projectName; + this.mTarget = target; + } + + /** + * Override parent to return stub page + */ + @Override + protected NewProjectCreationPage createMainPage() { + return new StubProjectCreationPage(mProjectName, mTarget); + } + + /** + * Override parent to return null page + */ + @Override + protected NewTestProjectCreationPage createTestPage() { + return null; + } + + /** + * Overrides parent to return dummy wizard container + */ + @Override + public IWizardContainer getContainer() { + return new IWizardContainer() { + + public IWizardPage getCurrentPage() { + return null; + } + + public Shell getShell() { + return null; + } + + public void showPage(IWizardPage page) { + // pass + } + + public void updateButtons() { + // pass + } + + public void updateMessage() { + // pass + } + + public void updateTitleBar() { + // pass + } + + public void updateWindowTitle() { + // pass + } + + /** + * Executes runnable on current thread + */ + public void run(boolean fork, boolean cancelable, + IRunnableWithProgress runnable) + throws InvocationTargetException, InterruptedException { + runnable.run(new NullProgressMonitor()); + } + + }; + } + } + + /** + * Stub class for project creation page. + * <p/> + * Returns canned responses for creating a sample project. + */ + public class StubProjectCreationPage extends NewProjectCreationPage { + + private final String mProjectName; + private final IAndroidTarget mTarget; + + public StubProjectCreationPage(String projectName, IAndroidTarget target) { + super(); + this.mProjectName = projectName; + this.mTarget = target; + setTestInfo(null); + } + + @Override + public IMainInfo getMainInfo() { + return new IMainInfo() { + public String getProjectName() { + return mProjectName; + } + + public String getPackageName() { + return "com.android.eclipse.tests"; + } + + public String getActivityName() { + return mProjectName; + } + + public String getApplicationName() { + return mProjectName; + } + + public boolean isNewProject() { + return true; + } + + public String getSourceFolder() { + return "src"; + } + + public IPath getLocationPath() { + // Default location + return null;//new Path(mLocation); + } + + public String getMinSdkVersion() { + return null; + } + + public IAndroidTarget getSdkTarget() { + return mTarget; + } + + public boolean isCreateActivity() { + return false; + } + + public boolean useDefaultLocation() { + return true; + } + + public IWorkingSet[] getSelectedWorkingSets() { + return new IWorkingSet[0]; + } + }; + } + } + + public void testDummy() { + // This class contains shared test functionality for testcase subclasses, + // but without an actual test in the class JUnit complains (even if we make + // it abstract) + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoringTest.java new file mode 100644 index 0000000..f822c62 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoringTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.FQCN_RELATIVE_LAYOUT; + +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.ltk.core.refactoring.Change; +import org.w3c.dom.Element; + +import java.util.Collections; +import java.util.List; + +public class ChangeLayoutRefactoringTest extends RefactoringTest { + + public void testChangeLayout1a() throws Exception { + // Test a basic layout which performs some nesting -- tests basic grid layout conversion + checkRefactoring("sample1a.xml", true); + } + + public void testChangeLayout1b() throws Exception { + // Same as 1a, but with different formatting to look for edit handling to for example + // remove a line that is made empty when its only attribute is removed + checkRefactoring("sample1b.xml", true); + } + + public void testChangeLayout2() throws Exception { + // Test code which analyzes an embedded RelativeLayout + checkRefactoring("sample2.xml", true); + } + + public void testChangeLayout3() throws Exception { + // Test handling of LinearLayout "weight" attributes on its children: the child with + // weight > 0 should fill and subsequent children attach on the bottom/right + checkRefactoring("sample3.xml", true); + } + + public void testChangeLayout4() throws Exception { + checkRefactoring("sample4.xml", true); + } + + public void testChangeLayout5() throws Exception { + // Test handling of LinearLayout "gravity" attributes on its children + checkRefactoring("sample5.xml", true); + } + + public void testChangeLayout6() throws Exception { + // Check handling of the LinearLayout "baseline" attribute + checkRefactoring("sample6.xml", true); + } + + private void checkRefactoring(String basename, boolean flatten) throws Exception { + IFile file = getLayoutFile(getProject(), basename); + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + CanvasViewInfo rootView = info.mRootView; + Element element = info.mElement; + + List<Element> selectedElements = Collections.singletonList(element); + ChangeLayoutRefactoring refactoring = new ChangeLayoutRefactoring(selectedElements, + layoutEditor); + refactoring.setFlatten(flatten); + refactoring.setType(FQCN_RELATIVE_LAYOUT); + refactoring.setRootView(rootView); + + List<Change> changes = refactoring.computeChanges(new NullProgressMonitor()); + checkEdits(basename, changes); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoringTest.java new file mode 100644 index 0000000..80307d2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeViewRefactoringTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.FQCN_RADIO_BUTTON; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.ltk.core.refactoring.Change; +import org.w3c.dom.Element; + +import java.util.List; + +public class ChangeViewRefactoringTest extends RefactoringTest { + + public void testChangeView1() throws Exception { + checkRefactoring("sample1a.xml", FQCN_RADIO_BUTTON, "@+id/button1", "@+id/button6"); + } + + public void testChangeView2() throws Exception { + // Tests (1) updating references to the renamed id of the changed widgets + // (e.g. button3 is renamed to imageButton1 and layout references to button3 + // must be updated), and (2) removal of attributes not available in the new type + // (the text property is removed since it is not available on the new widget + // type ImageButton) + checkRefactoring("sample2.xml", "android.widget.ImageButton", + "@+id/button3", "@+id/button5"); + } + + private void checkRefactoring(String basename, String newType, + String... ids) throws Exception { + assertTrue(ids.length > 0); + + IFile file = getLayoutFile(getProject(), basename); + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + List<Element> selectedElements = getElements(info.mElement, ids); + + ChangeViewRefactoring refactoring = new ChangeViewRefactoring(selectedElements, + layoutEditor); + refactoring.setType(newType); + + List<Change> changes = refactoring.computeChanges(new NullProgressMonitor()); + checkEdits(basename, changes); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoringTest.java new file mode 100644 index 0000000..da2a890 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoringTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.w3c.dom.Element; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExtractIncludeRefactoringTest extends RefactoringTest { + @Override + protected boolean testCaseNeedsUniqueProject() { + // Because some of these tests look at ALL layouts in the project + // to identify matches + return true; + } + + public void testExtract1() throws Exception { + // Basic: Extract a single button + checkRefactoring("sample3.xml", "newlayout1", false, null, 2, false /* diffs */, + "@+id/button2"); + } + + public void testExtract2() throws Exception { + // Extract a couple of elements + checkRefactoring("sample3.xml", "newlayout2", false, null, 2, false /* diffs */, + "@+id/button2", "@+id/android_logo"); + } + + public void testExtract3() throws Exception { + // Test to make sure layout attributes are updated + checkRefactoring("sample2.xml", "newlayout3", false, null, 2, false /* diffs */, + "@+id/button3"); + } + + public void testExtract4() throws Exception { + // Tests extracting from -multiple- files (as well as with custom android namespace + // prefix) + + // Make sure the variation-files exist + Map<IPath, String> extraFiles = new HashMap<IPath, String>(); + extraFiles.put(getTestDataFile(getProject(), "sample3-variation1.xml", + "res/layout-land/sample3.xml").getProjectRelativePath(), + "sample3-variation1.xml"); + extraFiles.put(getTestDataFile(getProject(), "sample3-variation2.xml", + "res/layout-xlarge-land/sample3.xml").getProjectRelativePath(), + "sample3-variation2.xml"); + + checkRefactoring("sample3.xml", "newlayout3", true, extraFiles, 4, false /* diffs */, + "@+id/android_logo"); + } + + public void testExtract5() throws Exception { + // Tests extracting from multiple files with -contiguous regions-. + + // Make sure the variation-files exist + Map<IPath, String> extraFiles = new HashMap<IPath, String>(); + extraFiles.put(getTestDataFile(getProject(), "sample3-variation1.xml", + "res/layout-land/sample3.xml").getProjectRelativePath(), + "sample3-variation1.xml"); + extraFiles.put(getTestDataFile(getProject(), "sample3-variation2.xml", + "res/layout-xlarge-land/sample3.xml").getProjectRelativePath(), + "sample3-variation2.xml"); + + checkRefactoring("sample3.xml", "newlayout3", true, extraFiles, 4, false /* diffs */, + "@+id/android_logo", "@+id/button1"); + } + + public void testExtract6() throws Exception { + // Tests extracting from multiple files where the layouts are completely + // different/unrelated files + + // Create the duplicate files + Map<IPath, String> extraFiles = new HashMap<IPath, String>(); + extraFiles.put(getTestDataFile(getProject(), "sample1a.xml", + "res/layout/sample1a.xml").getProjectRelativePath(), + "sample1a.xml"); + extraFiles.put(getTestDataFile(getProject(), "sample7.xml", "res/layout/sample7.xml") + .getProjectRelativePath(), "sample7.xml"); + extraFiles.put(getTestDataFile(getProject(), "sample8.xml", "res/layout/sample8.xml") + .getProjectRelativePath(), "sample8.xml"); + + checkRefactoring("sample7.xml", "newlayout6", true, extraFiles, 4, true /* diffs */, + "@+id/linearLayout4"); + } + + + private void checkRefactoring(String basename, String layoutName, + boolean replaceOccurrences, Map<IPath,String> extraFiles, + int expectedModifiedFileCount, boolean createDiffs, String... ids) throws Exception { + assertTrue(ids.length > 0); + + IFile file = getLayoutFile(getProject(), basename); + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + List<Element> selectedElements = getElements(info.mElement, ids); + + ExtractIncludeRefactoring refactoring = new ExtractIncludeRefactoring(selectedElements, + layoutEditor); + refactoring.setLayoutName(layoutName); + refactoring.setReplaceOccurrences(replaceOccurrences); + List<Change> changes = refactoring.computeChanges(new NullProgressMonitor()); + + assertTrue(changes.size() >= 3); + + Map<IPath,String> fileToGolden = new HashMap<IPath,String>(); + IPath sourcePath = file.getProjectRelativePath(); + fileToGolden.put(sourcePath, basename); + IPath newPath = sourcePath.removeLastSegments(1).append(layoutName + DOT_XML); + fileToGolden.put(newPath, layoutName + DOT_XML); + if (extraFiles != null) { + fileToGolden.putAll(extraFiles); + } + + checkEdits(changes, fileToGolden, createDiffs); + + int modifiedFileCount = 0; + for (Change change : changes) { + if (change instanceof TextFileChange) { + modifiedFileCount++; + } + } + assertEquals(expectedModifiedFileCount, modifiedFileCount); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java new file mode 100644 index 0000000..2802013 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoringTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import com.android.util.Pair; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.TextSelection; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ExtractStyleRefactoringTest extends RefactoringTest { + @Override + protected boolean testCaseNeedsUniqueProject() { + return true; + } + + public void testExtract1() throws Exception { + // Test extracting into a new style file + checkRefactoring("extractstyle1.xml", "newstyles.xml", "newstyle", + false /* removeExtracted */, false /* applyStyle */, null, 1, "@+id/button2"); + } + + public void testExtract1b() throws Exception { + // Extract and apply new style + checkRefactoring("extractstyle1.xml", "newstyles2.xml", "newstyle", + false /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract1c() throws Exception { + // Extract and remove extracted + checkRefactoring("extractstyle1.xml", "newstyles3.xml", "newstyle", + true /* removeExtracted */, false /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract1d() throws Exception { + // Extract and apply style and remove extracted + checkRefactoring("extractstyle1.xml", "newstyles4.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract2() throws Exception { + getTestDataFile(getProject(), "navigationstyles.xml", "res/values/navigationstyles.xml"); + + // -Modify- the existing styles.xml file + checkRefactoring("extractstyle1.xml", "navigationstyles.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, "@+id/button2"); + } + + public void testExtract3() throws Exception { + // Select multiple elements - overlap in values. + checkRefactoring("extractstyle1.xml", "newstyles4.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "@+id/button1", "@+id/button2"); + } + + // This test fails for some reason - not in the refactoring (checked manually) + // but the DOM model returns null when run in a test context. + public void testExtract4() throws Exception { + // Test extracting on a single caret position over an attribute: Should extract + // just that one attribute + checkRefactoringByOffset("extractstyle1.xml", "newstyles5.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "android:text^Color=\"#FF00FF\"", "android:text^Color=\"#FF00FF\""); + } + + public void testExtract5() throws Exception { + // Test extracting on a range selection inside an element: should extract just + // the attributes that overlap the selection + checkRefactoringByOffset("extractstyle1.xml", "newstyles6.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "android:^textSize=\"20pt", + "android:id=\"@+id/button1\" android:layout_a^lignParentBottom"); + } + + public void testExtract6() throws Exception { + // Test extracting on a single caret position which is not over any attributes: + checkRefactoringByOffset("extractstyle1.xml", "newstyles7.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 0, + "<Bu^tton", "<Bu^tton"); + } + + public void testExtract7() throws Exception { + // Verify that even with a different namespace prefix we end up with android: + // in the extracted style + checkRefactoring("extractstyle2.xml", "newstyles8.xml", "newstyle", + true /* removeExtracted */, true /* applyStyle */, null, 2, + "@+id/button1", "@+id/button2"); + } + + public void testExtract8() throws Exception { + // Test setting parent style + checkRefactoring("extractstyle1.xml", "newstyles3.xml", "newstyle", + true /* removeExtracted */, false /* applyStyle */, "android:Widget.Button", + 2, "@+id/button2"); + } + + // Check extract style on a selection of elements + private void checkRefactoring(String basename, String styleFileName, String newStyleName, + boolean removeExtracted, boolean applyStyle, String parentStyle, + int expectedModifiedFileCount, String... ids) throws Exception { + assertTrue(ids.length > 0); + + IFile file = getLayoutFile(getProject(), basename); + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + List<Element> selectedElements = getElements(info.mElement, ids); + + // Open the file such that ModelManager.getExistingModelForRead() in DomUtilities + // will succeed + IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); + IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); + IDE.openEditor(page, file); + + ExtractStyleRefactoring refactoring = new ExtractStyleRefactoring(selectedElements, + layoutEditor); + checkRefactoring(basename, styleFileName, newStyleName, removeExtracted, applyStyle, + parentStyle, expectedModifiedFileCount, file, refactoring); + } + + // Check extract style against a set of editor text locations + private void checkRefactoringByOffset(String basename, String styleFileName, + String newStyleName, boolean removeExtracted, boolean applyStyle, + String parentStyle, + int expectedModifiedFileCount, String beginCaretLocation, String endCaretLocation) + throws Exception { + IFile file = getLayoutFile(getProject(), basename); + int beginOffset = getCaretOffset(file, beginCaretLocation); + int endOffset = getCaretOffset(file, endCaretLocation); + + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + + // Open the file such that ModelManager.getExistingModelForRead() in DomUtilities + // will succeed + IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbenchWindow activeWorkbenchWindow = workbench.getActiveWorkbenchWindow(); + IWorkbenchPage page = activeWorkbenchWindow.getActivePage(); + IDE.openEditor(page, file); + + ITextSelection selection = new TextSelection(beginOffset, endOffset - beginOffset); + ExtractStyleRefactoring refactoring = new ExtractStyleRefactoring(file, + layoutEditor, selection, null); + checkRefactoring(basename, styleFileName, newStyleName, removeExtracted, applyStyle, + parentStyle, expectedModifiedFileCount, file, refactoring); + } + + // Common test code used by the other two check methods + private void checkRefactoring(String basename, String styleFileName, String newStyleName, + boolean removeExtracted, boolean applyStyle, String parentStyle, + int expectedModifiedFileCount, IFile file, + ExtractStyleRefactoring refactoring) throws Exception { + refactoring.setStyleName(newStyleName); + refactoring.setApplyStyle(applyStyle); + refactoring.setRemoveExtracted(removeExtracted); + refactoring.setStyleFileName(styleFileName); + refactoring.setParent(parentStyle); + + // Pick the attributes to extract -- for now everything (and where there are + // conflicting values, pick the first one) + Pair<Map<String, List<Attr>>, Set<Attr>> result = refactoring.getAvailableAttributes(); + Map<String, List<Attr>> availableAttributes = result.getFirst(); + Set<Attr> selected = result.getSecond(); + List<Attr> chosenAttributes = new ArrayList<Attr>(); + for (List<Attr> list : availableAttributes.values()) { + Collections.sort(list, new Comparator<Attr>() { + public int compare(Attr a1, Attr a2) { + return a1.getValue().compareTo(a2.getValue()); + } + }); + Attr attr = list.get(0); + if (selected.contains(attr)) { + chosenAttributes.add(attr); + } + } + refactoring.setChosenAttributes(chosenAttributes); + + List<Change> changes = refactoring.computeChanges(new NullProgressMonitor()); + assertEquals(expectedModifiedFileCount, changes.size()); + + Map<IPath,String> fileToGolden = new HashMap<IPath,String>(); + IPath sourcePath = file.getProjectRelativePath(); + fileToGolden.put(sourcePath, basename); + IPath newPath = refactoring.getStyleFile(getProject()).getProjectRelativePath(); + fileToGolden.put(newPath, styleFileName); + + checkEdits(changes, fileToGolden, true); + + int modifiedFileCount = 0; + for (Change change : changes) { + if (change instanceof TextFileChange) { + modifiedFileCount++; + } + } + assertEquals(expectedModifiedFileCount, modifiedFileCount); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java new file mode 100644 index 0000000..498f65a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistantTest.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.AndroidConstants.FD_RES_LAYOUT; +import static com.android.sdklib.SdkConstants.FD_RES; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; + +public class RefactoringAssistantTest extends AdtProjectTest { + public void testAssistant1() throws Exception { + // "Extract String" + checkFixes("sample1a.xml", "<Button android:text=\"Fir^stButton\""); + } + + public void testAssistant2() throws Exception { + // Visual refactoring operations + checkFixes("sample1a.xml", "<Bu^tton android:text"); + } + + public void testAssistant3() throws Exception { + // Negative test: ensure that we don't get completion items in other parts of the XML + checkFixes("sample1a.xml", "<Button andr^oid:text=\"FirstButton\""); + } + + public void testAssistant4() throws Exception { + // Negative test: ensure that we don't offer extract string on a value that is + // already a resource + checkFixes("sample1a.xml", "android:id=\"@+id/Linea^rLayout2\""); + } + + private void checkFixes(String name, String caretLocation) + throws Exception { + IProject project = getProject(); + IFile file = getTestDataFile(project, name, FD_RES + "/" + FD_RES_LAYOUT + "/" + name); + + // Determine the offset + String fileContent = AdtPlugin.readFile(file); + int caretDelta = caretLocation.indexOf("^"); + assertTrue(caretLocation, caretDelta != -1); + String caretContext = caretLocation.substring(0, caretDelta) + + caretLocation.substring(caretDelta + "^".length()); + int caretContextIndex = fileContent.indexOf(caretContext); + assertTrue("Caret content " + caretContext + " not found in file", + caretContextIndex != -1); + final int offset = caretContextIndex + caretDelta; + + + RefactoringAssistant refactoringAssistant = new RefactoringAssistant(); + + // Open file + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + assertNotNull(page); + IEditorPart editor = IDE.openEditor(page, file); + assertTrue(editor instanceof AndroidXmlEditor); + AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor; + final ISourceViewer viewer = layoutEditor.getStructuredSourceViewer(); + + IQuickAssistInvocationContext invocationContext = new IQuickAssistInvocationContext() { + public int getLength() { + return 0; + } + + public int getOffset() { + return offset; + } + + public ISourceViewer getSourceViewer() { + return viewer; + } + }; + ICompletionProposal[] proposals = refactoringAssistant + .computeQuickAssistProposals(invocationContext); + + if (proposals != null) { + for (ICompletionProposal proposal : proposals) { + assertNotNull(proposal.getAdditionalProposalInfo()); + assertNotNull(proposal.getImage()); + } + } + + StringBuilder sb = new StringBuilder(1000); + sb.append("Quick assistant in " + name + " for " + caretLocation + ":\n"); + if (proposals != null) { + for (ICompletionProposal proposal : proposals) { + sb.append(proposal.getDisplayString()); + String help = proposal.getAdditionalProposalInfo(); + if (help != null && help.trim().length() > 0) { + sb.append(" : "); + sb.append(help.replace('\n', ' ')); + } + sb.append('\n'); + } + } else { + sb.append("None found.\n"); + } + assertEqualsGolden(name, sb.toString(), "txt"); + + // No "apply" test on these assists since they are interactive. Refactoring + // is tested elsewhere. + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java new file mode 100644 index 0000000..4ca30d6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.ANDROID_WIDGET_PREFIX; +import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML; + +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.TextFileChange; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.TextEdit; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Element; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@SuppressWarnings("restriction") +public class RefactoringTest extends AdtProjectTest { + + protected static Element findElementById(Element root, String id) { + if (id.equals(VisualRefactoring.getId(root))) { + return root; + } + + for (Element child : DomUtilities.getChildren(root)) { + Element result = findElementById(child, id); + if (result != null) { + return result; + } + } + + return null; + } + + protected static List<Element> getElements(Element root, String... ids) { + List<Element> selectedElements = new ArrayList<Element>(); + for (String id : ids) { + Element element = findElementById(root, id); + assertNotNull(element); + selectedElements.add(element); + } + return selectedElements; + } + + protected void checkEdits(String basename, List<Change> changes) throws BadLocationException, + IOException { + IDocument document = new Document(); + + String xml = readTestFile(basename, false); + if (xml == null) { // New file + xml = ""; //$NON-NLS-1$ + } + document.set(xml); + + for (Change change : changes) { + if (change instanceof TextFileChange) { + TextFileChange tf = (TextFileChange) change; + TextEdit edit = tf.getEdit(); + if (edit instanceof MultiTextEdit) { + MultiTextEdit edits = (MultiTextEdit) edit; + edits.apply(document); + } else { + edit.apply(document); + } + } else { + System.out.println("Ignoring non-textfilechange in refactoring result"); + } + } + + String actual = document.get(); + assertEqualsGolden(basename, actual); + } + + protected void checkEdits(List<Change> changes, + Map<IPath, String> fileToGoldenName) throws BadLocationException { + checkEdits(changes, fileToGoldenName, false); + } + + protected void checkEdits(List<Change> changes, + Map<IPath, String> fileToGoldenName, boolean createDiffs) throws BadLocationException { + for (Change change : changes) { + if (change instanceof TextFileChange) { + TextFileChange tf = (TextFileChange) change; + IFile file = tf.getFile(); + assertNotNull(file); + IPath path = file.getProjectRelativePath(); + String goldenName = fileToGoldenName.get(path); + assertNotNull("Not found: " + path.toString(), goldenName); + + String xml = readTestFile(goldenName, false); + if (xml == null) { // New file + xml = ""; //$NON-NLS-1$ + } + IDocument document = new Document(); + document.set(xml); + + String before = document.get(); + + TextEdit edit = tf.getEdit(); + if (edit instanceof MultiTextEdit) { + MultiTextEdit edits = (MultiTextEdit) edit; + edits.apply(document); + } else { + edit.apply(document); + } + + String actual = document.get(); + + if (createDiffs) { + // Use a diff as the golden file instead of the after + actual = getDiff(before, actual); + if (goldenName.endsWith(DOT_XML)) { + goldenName = goldenName.substring(0, + goldenName.length() - DOT_XML.length()) + + ".diff"; + } + } + + assertEqualsGolden(goldenName, actual); + } else { + System.out.println("Ignoring non-textfilechange in refactoring result"); + assertNull(change.getAffectedObjects()); + } + } + } + + protected UiViewElementNode createModel(UiViewElementNode parent, Element element) { + List<Element> children = DomUtilities.getChildren(element); + String fqcn = ANDROID_WIDGET_PREFIX + element.getTagName(); + boolean hasChildren = children.size() > 0; + UiViewElementNode node = createNode(parent, fqcn, hasChildren); + node.setXmlNode(element); + for (Element child : children) { + createModel(node, child); + } + + return node; + } + + /** + * Builds up a ViewInfo hierarchy for the given model. This is done by + * reading .info dump files which record the exact pixel sizes of each + * ViewInfo object. These files are assumed to match up exactly with the + * model objects. This is done rather than rendering an actual layout + * hierarchy to insulate the test from pixel difference (in say font size) + * among platforms, as well as tying the test to particulars about relative + * sizes of things which may change with theme adjustments etc. + * <p> + * Each file can be generated by the dump method in the ViewHierarchy. + */ + protected ViewInfo createInfos(UiElementNode model, String relativePath) { + String basename = relativePath.substring(0, relativePath.lastIndexOf('.') + 1); + String relative = basename + "info"; //$NON-NLS-1$ + String info = readTestFile(relative, true); + // Parse the info file and build up a model from it + // Each line contains a new info. + // If indented it is a child of the parent. + String[] lines = info.split("\n"); //$NON-NLS-1$ + + // Iteration order for the info file should match exactly the UI model so + // we can just advance the line index sequentially as we traverse + + return create(model, Arrays.asList(lines).iterator()); + } + + protected ViewInfo create(UiElementNode node, Iterator<String> lineIterator) { + // android.widget.LinearLayout [0,36,240,320] + Pattern pattern = Pattern.compile("(\\s*)(\\S+) \\[(\\d+),(\\d+),(\\d+),(\\d+)\\].*"); + assertTrue(lineIterator.hasNext()); + String description = lineIterator.next(); + Matcher matcher = pattern.matcher(description); + assertTrue(matcher.matches()); + //String indent = matcher.group(1); + //String fqcn = matcher.group(2); + String left = matcher.group(3); + String top = matcher.group(4); + String right = matcher.group(5); + String bottom = matcher.group(6); + + ViewInfo view = new ViewInfo(node.getXmlNode().getLocalName(), node, + Integer.parseInt(left), Integer.parseInt(top), + Integer.parseInt(right), Integer.parseInt(bottom)); + + List<UiElementNode> childNodes = node.getUiChildren(); + if (childNodes.size() > 0) { + List<ViewInfo> children = new ArrayList<ViewInfo>(); + for (UiElementNode child : childNodes) { + children.add(create(child, lineIterator)); + } + view.setChildren(children); + } + + return view; + } + + protected TestContext setupTestContext(IFile file, String relativePath) throws Exception { + IStructuredModel structuredModel = null; + org.w3c.dom.Document domDocument = null; + IStructuredDocument structuredDocument = null; + Element element = null; + + try { + IModelManager modelManager = StructuredModelManager.getModelManager(); + structuredModel = modelManager.getModelForRead(file); + if (structuredModel instanceof IDOMModel) { + IDOMModel domModel = (IDOMModel) structuredModel; + domDocument = domModel.getDocument(); + element = domDocument.getDocumentElement(); + structuredDocument = structuredModel.getStructuredDocument(); + } + } finally { + if (structuredModel != null) { + structuredModel.releaseFromRead(); + } + } + + assertNotNull(structuredModel); + assertNotNull(domDocument); + assertNotNull(element); + assertNotNull(structuredDocument); + assertTrue(element instanceof IndexedRegion); + + UiViewElementNode model = createModel(null, element); + ViewInfo info = createInfos(model, relativePath); + CanvasViewInfo rootView = CanvasViewInfo.create(info, true /* layoutlib5 */).getFirst(); + TestLayoutEditor layoutEditor = new TestLayoutEditor(file, structuredDocument, null); + + TestContext testInfo = createTestContext(); + testInfo.mFile = file; + testInfo.mStructuredModel = structuredModel; + testInfo.mStructuredDocument = structuredDocument; + testInfo.mElement = element; + testInfo.mDomDocument = domDocument; + testInfo.mUiModel = model; + testInfo.mViewInfo = info; + testInfo.mRootView = rootView; + testInfo.mLayoutEditor = layoutEditor; + + return testInfo; + } + + protected TestContext createTestContext() { + return new TestContext(); + } + + protected static class TestContext { + protected IFile mFile; + protected IStructuredModel mStructuredModel; + protected IStructuredDocument mStructuredDocument; + protected org.w3c.dom.Document mDomDocument; + protected Element mElement; + protected UiViewElementNode mUiModel; + protected ViewInfo mViewInfo; + protected CanvasViewInfo mRootView; + protected TestLayoutEditor mLayoutEditor; + } + + @Override + public void testDummy() { + // To avoid JUnit warning that this class contains no tests, even though + // this is an abstract class and JUnit shouldn't try + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoringTest.java new file mode 100644 index 0000000..26d908b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoringTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.refactoring; + +import static com.android.ide.common.layout.LayoutConstants.FQCN_GESTURE_OVERLAY_VIEW; +import static com.android.ide.common.layout.LayoutConstants.FQCN_LINEAR_LAYOUT; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.ltk.core.refactoring.Change; +import org.w3c.dom.Element; + +import java.util.List; + +public class WrapInRefactoringTest extends RefactoringTest { + + public void testWrapIn1() throws Exception { + // Test wrapping view: should indent view + checkRefactoring("sample3.xml", FQCN_LINEAR_LAYOUT, "@+id/button2"); + } + + public void testWrapIn2() throws Exception { + // Test wrapping the root: should move namespace + checkRefactoring("sample3.xml", FQCN_GESTURE_OVERLAY_VIEW, "@+id/newlinear"); + } + + public void testWrapIn3() throws Exception { + // Test wrap multiple adjacent elements - should wrap all as a unit + checkRefactoring("sample3.xml", FQCN_LINEAR_LAYOUT, "@+id/button2", "@+id/android_logo"); + } + + private void checkRefactoring(String basename, String fqcn, String... ids) throws Exception { + assertTrue(ids.length > 0); + + IFile file = getLayoutFile(getProject(), basename); + TestContext info = setupTestContext(file, basename); + TestLayoutEditor layoutEditor = info.mLayoutEditor; + List<Element> selectedElements = getElements(info.mElement, ids); + + WrapInRefactoring refactoring = new WrapInRefactoring(selectedElements, + layoutEditor); + refactoring.setType(fqcn); + List<Change> changes = refactoring.computeChanges(new NullProgressMonitor()); + checkEdits(basename, changes); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml new file mode 100644 index 0000000..d72f4ba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror1.xml @@ -0,0 +1,12 @@ +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="50pt" + android:layout_marginLeft="50dp" + android:layout_marginBottom="50" + /> +</FrameLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml new file mode 100644 index 0000000..b720daa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror2.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <style name="repeatedStyle1"> + <item name="android:gravity">left</item> + </style> + <style name="repeatedStyle1"> + <item name="android:gravity">bottom</item> + </style> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml new file mode 100644 index 0000000..bc9c134 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror3.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <style name="wrongAttribute"> + <item name="nonexistent">5</item> + </style> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml new file mode 100644 index 0000000..28dd467 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror4.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style> + <item /> + </style> + <item></item> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml new file mode 100644 index 0000000..ee89ac4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror5.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="test"> + <item name="android:layout_width"></item> + </style> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml new file mode 100644 index 0000000..e552ff7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror6.xml @@ -0,0 +1,11 @@ +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="" + android:layout_marginLeft='' + /> +</FrameLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml new file mode 100644 index 0000000..d47f4ae --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/aapterror7.xml @@ -0,0 +1,10 @@ +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + <TextView + android:id="" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + /> +</FrameLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion53.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion53.txt new file mode 100644 index 0000000..3e44918 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion53.txt @@ -0,0 +1,6 @@ +Code completion in anim1.xml for ^<set xmlns: +<alpha /> +<rotate /> +<scale /> +<set ></set> +<translate /> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion54.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion54.txt new file mode 100644 index 0000000..f5e5cba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion54.txt @@ -0,0 +1,6 @@ +Code completion in anim1.xml for ^<translate android:id=: +<alpha /> +<rotate /> +<scale /> +<set ></set> +<translate /> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt new file mode 100644 index 0000000..28b15d3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt @@ -0,0 +1,16 @@ +Code completion in anim1.xml for android:^fromXDelta=: +android:fromXDelta : [float, fraction] +android:toXDelta : [float, fraction] +android:fromYDelta : [float, fraction] +android:toYDelta : [float, fraction] +android:interpolator : Defines the interpolator used to smooth the animation movement in time. [reference] +android:fillEnabled : When set to true, fillAfter is taken into account. [boolean] +android:fillBefore : When set to true, the animation transformation is applied before the animation has started. [boolean] +android:fillAfter : When set to true, the animation transformation is applied after the animation is over. [boolean] +android:duration : Amount of time (in milliseconds) for the animation to run. [integer] +android:startOffset : Delay in milliseconds before the animation runs, once start time is reached. [integer] +android:repeatCount : Defines how many times the animation should repeat. [integer, enum] +android:repeatMode : Defines the animation behavior when it reaches the end and the repeat count is greater than 0 or infinite. [enum] +android:zAdjustment : Allows for an adjustment of the Z ordering of the content being animated for the duration of the animation. [enum] +android:background : Special background behind animation. [reference, color] +android:detachWallpaper : Special option for window animations: if this window is on top of a wallpaper, don't animate the wallpaper with it. [boolean] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt new file mode 100644 index 0000000..632a9c5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt @@ -0,0 +1,14 @@ +Code completion in anim1.xml for android:^fromAlpha=: +android:fromAlpha : [float] +android:toAlpha : [float] +android:interpolator : Defines the interpolator used to smooth the animation movement in time. [reference] +android:fillEnabled : When set to true, fillAfter is taken into account. [boolean] +android:fillBefore : When set to true, the animation transformation is applied before the animation has started. [boolean] +android:fillAfter : When set to true, the animation transformation is applied after the animation is over. [boolean] +android:duration : Amount of time (in milliseconds) for the animation to run. [integer] +android:startOffset : Delay in milliseconds before the animation runs, once start time is reached. [integer] +android:repeatCount : Defines how many times the animation should repeat. [integer, enum] +android:repeatMode : Defines the animation behavior when it reaches the end and the repeat count is greater than 0 or infinite. [enum] +android:zAdjustment : Allows for an adjustment of the Z ordering of the content being animated for the duration of the animation. [enum] +android:background : Special background behind animation. [reference, color] +android:detachWallpaper : Special option for window animations: if this window is on top of a wallpaper, don't animate the wallpaper with it. [boolean] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion57.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion57.txt new file mode 100644 index 0000000..9225dac --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion57.txt @@ -0,0 +1,3 @@ +Code completion in anim1.xml for android:fromXDelta="100^%p": +100% : <b>Fraction</b> - a percentage of the base size +100%p : <b>Fraction</b> - a percentage relative to parent container diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1.xml new file mode 100644 index 0000000..48fefc2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1.xml @@ -0,0 +1,20 @@ +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <translate android:id="@+id/test1" + android:fromXDelta="100%p" + android:pivotY="60%p" + android:toXDelta="40%p" + android:toYDelta="33%p" + android:fillBefore="true" + android:fillAfter="true" + android:startOffset="1000" + android:duration="1000" /> + <alpha + android:id="@+id/test2" + android:fromAlpha="1.0" + android:toAlpha="0.0" + android:startOffset="3000" + android:duration="250" + android:fillBefore="true" + android:fillAfter="false" + /> +</set> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion58.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion58.txt new file mode 100644 index 0000000..0759415 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion58.txt @@ -0,0 +1,4 @@ +Code completion in animator1.xml for ^<set xmlns: +<animator ></animator> +<objectAnimator ></objectAnimator> +<set ></set> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion59.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion59.txt new file mode 100644 index 0000000..0702712 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion59.txt @@ -0,0 +1,10 @@ +Code completion in animator1.xml for android:^duration="2000": +android:propertyName : Name of the property being animated. [string] +android:interpolator : Defines the interpolator used to smooth the animation movement in time. [reference] +android:duration : Amount of time (in milliseconds) for the animation to run. [integer] +android:startOffset : Delay in milliseconds before the animation runs, once start time is reached. [integer] +android:repeatCount : Defines how many times the animation should repeat. [integer, enum] +android:repeatMode : Defines the animation behavior when it reaches the end and the repeat count is greater than 0 or infinite. [enum] +android:valueFrom : Value the animation starts from. [integer, float, color, dimension] +android:valueTo : Value the animation animates to. [integer, float, color, dimension] +android:valueType : The type of valueFrom and valueTo. [enum] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion60.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion60.txt new file mode 100644 index 0000000..3e5e6b1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion60.txt @@ -0,0 +1,3 @@ +Code completion in animator1.xml for android:propertyName="scal^eX"/>: +scaleX : scale of the view in the x direction. +scaleY : scale of the view in the y direction. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt new file mode 100644 index 0000000..d4618c2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt @@ -0,0 +1,18 @@ +Code completion in animator1.xml for android:interpolator="^@android:anim/bounce_interpolator": +@android:anim/accelerate_decelerate_interpolator +@android:anim/accelerate_interpolator +@android:anim/decelerate_interpolator +@android:anim/anticipate_interpolator +@android:anim/overshoot_interpolator +@android:anim/anticipate_overshoot_interpolator +@android:anim/bounce_interpolator +@android:anim/linear_interpolator +@android:anim/cycle_interpolator +@android: +@anim/ +@animator/ +@color/ +@drawable/ +@layout/ +@string/ +@style/ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1.xml new file mode 100644 index 0000000..bdf10dc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1.xml @@ -0,0 +1,27 @@ +<!-- Simple bounce animation --> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:ordering="sequentially"> + <set> + <objectAnimator + android:duration="2000" + android:valueTo="310" + android:propertyName="x"/> + <objectAnimator + android:duration="2000" + android:valueTo="130" + android:propertyName="y" + android:interpolator="@android:anim/bounce_interpolator"/> + <objectAnimator + android:duration="2000" + android:valueTo=".4" + android:propertyName="scaleX"/> + <objectAnimator + android:duration="2000" + android:valueTo=".4" + android:propertyName="scaleY"/> + </set> + <objectAnimator + android:duration="500" + android:valueTo="0" + android:propertyName="alpha"/> +</set> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-applyCompletion15.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-applyCompletion15.diff new file mode 100644 index 0000000..51a2cc9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-applyCompletion15.diff @@ -0,0 +1,4 @@ +Code completion in broken1.xml for android:textColorHigh^ selecting android:textColorHighlight: +< android:textColorHigh^ +--- +> android:textColorHighlight="^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt new file mode 100644 index 0000000..0a4c2f4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt @@ -0,0 +1,2 @@ +Code completion in broken1.xml for android:textColorHigh^: +android:textColorHighlight : Color of the text selection highlight. [reference, color] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1.xml new file mode 100644 index 0000000..161b981 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:text="@string/app_name" + android:textColorHigh + android:layout_marginLeft="@android:dimen/app_icon_size" + android:id="@+id/button1" + ></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-applyCompletion16.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-applyCompletion16.diff new file mode 100644 index 0000000..21437b9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-applyCompletion16.diff @@ -0,0 +1,4 @@ +Code completion in broken2.xml for style=^ selecting "@android:": +< style=^ +--- +> style="@android:^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt new file mode 100644 index 0000000..96e8408 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2-expected-completion21.txt @@ -0,0 +1,5 @@ +Code completion in broken2.xml for style=^: +"@android:" +"@drawable/" +"@layout/" +"@string/" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2.xml new file mode 100644 index 0000000..60644b9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken2.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:text="@string/app_name" + android:layout_marginLeft="@android:dimen/app_icon_size" + style= + android:id="@+id/button1" + ></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3-expected-applyCompletion14.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3-expected-applyCompletion14.diff new file mode 100644 index 0000000..3e60eb9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3-expected-applyCompletion14.diff @@ -0,0 +1,4 @@ +Code completion in broken3.xml for <EditT^ selecting EditText />: +< <EditT^ +--- +> <EditText ^/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3-expected-completion19.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3-expected-completion19.txt new file mode 100644 index 0000000..ccc6a4c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3-expected-completion19.txt @@ -0,0 +1,2 @@ +Code completion in broken3.xml for <EditT^: +EditText /> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3.xml new file mode 100644 index 0000000..b8b1685 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken3.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <EditT + <Button + android:text="@string/app_name" + android:layout_marginLeft="@android:dimen/app_icon_size" + android:id="@+id/button1" + ></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion45.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion45.txt new file mode 100644 index 0000000..c799b80 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion45.txt @@ -0,0 +1,2 @@ +Code completion in color1.xml for ^<selector: +<selector ></selector> : Required. This must be the root element. Contains one or more <item> elements. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion46a.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion46a.txt new file mode 100644 index 0000000..32f0066 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion46a.txt @@ -0,0 +1,2 @@ +Code completion in color1.xml for ^<item android: +<item /> : Drawable states. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion46b.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion46b.txt new file mode 100644 index 0000000..7fcc5a9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1-expected-completion46b.txt @@ -0,0 +1,16 @@ +Code completion in color1.xml for <item ^android:state_focused="true"/>: +android:state_focused : State value for StateListDrawable, set when a view has input focus. [boolean] +android:state_window_focused : State value for StateListDrawable, set when a view's window has input focus. [boolean] +android:state_enabled : State value for StateListDrawable, set when a view is enabled. [boolean] +android:state_checkable : State identifier indicating that the object <var>may</var> display a check mark. [boolean] +android:state_checked : State identifier indicating that the object is currently checked. [boolean] +android:state_selected : State value for StateListDrawable, set when a view (or one of its parents) is currently selected. [boolean] +android:state_pressed : State value for StateListDrawable, set when the user is pressing down in a view. [boolean] +android:state_activated : State value for StateListDrawable, set when a view or its parent has been "activated" meaning the user has currently marked it as being of interest. [boolean] +android:state_active : State value for StateListDrawable. [boolean] +android:state_single : State value for StateListDrawable. [boolean] +android:state_first : State value for StateListDrawable. [boolean] +android:state_middle : State value for StateListDrawable. [boolean] +android:state_last : State value for StateListDrawable. [boolean] +android:state_accelerated : State value for StateListDrawable, indicating that the Drawable is in a view that is hardware accelerated. [boolean] +android:color : Hexadeximal color. Required. The color is specified with an RGB value and optional alpha channel. The value always begins with a pound (#) character and then followed by the Alpha-Red-Green-Blue information in one of the following formats: * RGB * ARGB * RRGGBB * AARRGGBB diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1.xml new file mode 100644 index 0000000..a8482ab --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/color1.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector + xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true"/> + <item /> +</selector> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-actual-applyCompletion1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-actual-applyCompletion1.xml new file mode 100644 index 0000000..2413658 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-actual-applyCompletion1.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> +<!-- + This file deliberately contains errors - it represents partial keyboard + typing for interactive code completion +--> + <TextView + android:layout_weight^="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@android:dimen/app_icon_size" + android:layout_marginLeft="50dp" + android:layout_marginBottom="50" + android:textColor="#000000" + style="@android:style/Widget.Button" + android:gravity="left|bottom" + android:text="@string/hello" + android:hint="hint" /> + <FrameLayout android:foreground="@android:drawable/btn_default"></FrameLayout> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion1.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion1.diff new file mode 100644 index 0000000..d656509 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion1.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_w^idth="fill_parent" selecting android:layout_weight: +< android:layout_w^idth="fill_parent" +--- +> android:layout_weight^="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion10.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion10.diff new file mode 100644 index 0000000..824fa25 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion10.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for <T^extView selecting TableLayout: +< <T^extView +--- +> <TableLayout^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion11a.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion11a.diff new file mode 100644 index 0000000..7f4ec86 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion11a.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for ^<TextView selecting <RadioGroup ></RadioGroup>: +< ^<TextView +--- +> <RadioGroup ^></RadioGroup><TextView diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion11b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion11b.diff new file mode 100644 index 0000000..384c4a9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion11b.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for ^<TextView selecting <CheckBox />: +< ^<TextView +--- +> <CheckBox ^/><TextView diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion12.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion12.diff new file mode 100644 index 0000000..a4b7231 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion12.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for btn_default">^</FrameLayout> selecting <FrameLayout ></FrameLayout>: +< <FrameLayout android:foreground="@android:drawable/btn_default">^</FrameLayout> +--- +> <FrameLayout android:foreground="@android:drawable/btn_default"><FrameLayout ^></FrameLayout></FrameLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion2.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion2.diff new file mode 100644 index 0000000..a410606 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion2.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_width="^fill_parent" selecting match_parent: +< android:layout_width="^fill_parent" +--- +> android:layout_width="match_parent"^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion3.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion3.diff new file mode 100644 index 0000000..578f8ea --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion3.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_width="fi^ll_parent" selecting fill_parent: +< android:layout_width="fi^ll_parent" +--- +> android:layout_width="fill_parent"^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion39.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion39.diff new file mode 100644 index 0000000..577089b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion39.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for marginBottom="50"^ selecting android:maxEms: +< android:layout_marginBottom="50"^ +--- +> android:layout_marginBottom="50" android:maxEms="^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion4.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion4.diff new file mode 100644 index 0000000..ebbba89 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion4.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for marginBottom="50^" selecting 50mm: +< android:layout_marginBottom="50^" +--- +> android:layout_marginBottom="50mm"^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion5.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion5.diff new file mode 100644 index 0000000..ba7cb0b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion5.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_marginLeft="50d^p" selecting 50dp: +< android:layout_marginLeft="50d^p" +--- +> android:layout_marginLeft="50dp"^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion6.diff new file mode 100644 index 0000000..f1e6465 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion6.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for style="@android:^style/Widget.Button" selecting @android:drawable/: +< style="@android:^style/Widget.Button" +--- +> style="@android:drawable/^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion7a.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion7a.diff new file mode 100644 index 0000000..1a577db --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion7a.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for android:gravity="l^eft|bottom" selecting left: +< android:gravity="l^eft|bottom" +--- +> android:gravity="left^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion7b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion7b.diff new file mode 100644 index 0000000..2560011 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion7b.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for android:gravity="left|b^ottom" selecting bottom: +< android:gravity="left|b^ottom" +--- +> android:gravity="left|bottom^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion8.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion8.diff new file mode 100644 index 0000000..655afc5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion8.diff @@ -0,0 +1,2 @@ +Code completion in completion1.xml for layout_width^="fill_parent" selecting android:layout_width: +No changes
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion9.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion9.diff new file mode 100644 index 0000000..05656fb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-applyCompletion9.diff @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_width=^"fill_parent" selecting "wrap_content": +< android:layout_width=^"fill_parent" +--- +> android:layout_width="wrap_content"^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion1.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion1.txt new file mode 100644 index 0000000..949067a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion1.txt @@ -0,0 +1,3 @@ +Code completion in completion1.xml for layout_w^idth="fill_parent": +android:layout_width : Specifies the basic width of the view. [dimension, enum] +android:layout_weight : [float] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion10.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion10.txt new file mode 100644 index 0000000..68efdfb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion10.txt @@ -0,0 +1,10 @@ +Code completion in completion1.xml for <T^extView: +TabHost +TabWidget +TableLayout +TableRow +TextSwitcher +TextView +TimePicker +ToggleButton +TwoLineListItem diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt new file mode 100644 index 0000000..9c1e396 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion11.txt @@ -0,0 +1,60 @@ +Code completion in completion1.xml for ^<TextView: +<AbsoluteLayout ></AbsoluteLayout> +<AdapterViewFlipper ></AdapterViewFlipper> +<AnalogClock /> +<AutoCompleteTextView /> +<Button /> +<CalendarView /> +<CheckBox /> +<CheckedTextView /> +<Chronometer /> +<DatePicker /> +<DialerFilter ></DialerFilter> +<DigitalClock /> +<EditText /> +<ExpandableListView ></ExpandableListView> +<FrameLayout ></FrameLayout> +<Gallery /> +<GestureOverlayView /> : GestureOverlayView specific attributes. +<GridView ></GridView> +<HorizontalScrollView ></HorizontalScrollView> +<ImageButton /> +<ImageSwitcher ></ImageSwitcher> +<ImageView /> +<LinearLayout ></LinearLayout> +<ListView ></ListView> +<MediaController ></MediaController> +<MultiAutoCompleteTextView /> +<NumberPicker /> +<ProgressBar /> +<QuickContactBadge /> +<RadioButton /> +<RadioGroup ></RadioGroup> +<RatingBar /> +<RelativeLayout ></RelativeLayout> +<ScrollView ></ScrollView> +<SearchView ></SearchView> +<SeekBar /> +<SlidingDrawer ></SlidingDrawer> : SlidingDrawer specific attributes. +<Spinner /> +<StackView ></StackView> +<SurfaceView /> +<TabHost ></TabHost> +<TabWidget ></TabWidget> +<TableLayout ></TableLayout> +<TableRow ></TableRow> +<TextSwitcher ></TextSwitcher> +<TextView /> +<TimePicker /> +<ToggleButton /> +<TwoLineListItem /> +<VideoView /> +<View /> : Attributes that can be used with android.view.View or any of its subclasses. +<ViewAnimator ></ViewAnimator> +<ViewFlipper ></ViewFlipper> +<ViewStub /> : A android.view.ViewStub lets you lazily include other XML layouts inside your application at runtime. +<ViewSwitcher ></ViewSwitcher> +<WebView /> +<ZoomButton /> +<ZoomControls /> +<include /> : Lets you statically include XML layouts inside other XML layouts. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt new file mode 100644 index 0000000..fd41c10 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion12.txt @@ -0,0 +1,60 @@ +Code completion in completion1.xml for btn_default">^</FrameLayout>: +<AbsoluteLayout ></AbsoluteLayout> +<AdapterViewFlipper ></AdapterViewFlipper> +<AnalogClock /> +<AutoCompleteTextView /> +<Button /> +<CalendarView /> +<CheckBox /> +<CheckedTextView /> +<Chronometer /> +<DatePicker /> +<DialerFilter ></DialerFilter> +<DigitalClock /> +<EditText /> +<ExpandableListView ></ExpandableListView> +<FrameLayout ></FrameLayout> +<Gallery /> +<GestureOverlayView /> : GestureOverlayView specific attributes. +<GridView ></GridView> +<HorizontalScrollView ></HorizontalScrollView> +<ImageButton /> +<ImageSwitcher ></ImageSwitcher> +<ImageView /> +<LinearLayout ></LinearLayout> +<ListView ></ListView> +<MediaController ></MediaController> +<MultiAutoCompleteTextView /> +<NumberPicker /> +<ProgressBar /> +<QuickContactBadge /> +<RadioButton /> +<RadioGroup ></RadioGroup> +<RatingBar /> +<RelativeLayout ></RelativeLayout> +<ScrollView ></ScrollView> +<SearchView ></SearchView> +<SeekBar /> +<SlidingDrawer ></SlidingDrawer> : SlidingDrawer specific attributes. +<Spinner /> +<StackView ></StackView> +<SurfaceView /> +<TabHost ></TabHost> +<TabWidget ></TabWidget> +<TableLayout ></TableLayout> +<TableRow ></TableRow> +<TextSwitcher ></TextSwitcher> +<TextView /> +<TimePicker /> +<ToggleButton /> +<TwoLineListItem /> +<VideoView /> +<View /> : Attributes that can be used with android.view.View or any of its subclasses. +<ViewAnimator ></ViewAnimator> +<ViewFlipper ></ViewFlipper> +<ViewStub /> : A android.view.ViewStub lets you lazily include other XML layouts inside your application at runtime. +<ViewSwitcher ></ViewSwitcher> +<WebView /> +<ZoomButton /> +<ZoomControls /> +<include /> : Lets you statically include XML layouts inside other XML layouts. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion2.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion2.txt new file mode 100644 index 0000000..136a6fe --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion2.txt @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_width="^fill_parent": +fill_parent +match_parent +wrap_content diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion3.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion3.txt new file mode 100644 index 0000000..09c27ce --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion3.txt @@ -0,0 +1,2 @@ +Code completion in completion1.xml for layout_width="fi^ll_parent": +fill_parent diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt new file mode 100644 index 0000000..d03f129 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt @@ -0,0 +1,142 @@ +Code completion in completion1.xml for marginBottom="50"^: + style : A reference to a custom style [reference] + android:bufferType : Determines the minimum type that getText() will return. [enum] + android:text : Text to display. [string] + android:hint : Hint text to display when the text is empty. [string] + android:textColor : Text color. [reference, color] + android:textColorHighlight : Color of the text selection highlight. [reference, color] + android:textColorHint : Color of the hint text. [reference, color] + android:textAppearance : Base text color, typeface, size, and style. [reference] + android:textSize : Size of the text. [dimension] + android:textScaleX : Sets the horizontal scaling factor for the text. [float] + android:typeface : Typeface (normal, sans, serif, monospace) for the text. [enum] + android:textStyle : Style (bold, italic, bolditalic) for the text. [flag] + android:textColorLink : Text color for links. [reference, color] + android:cursorVisible : Makes the cursor visible (the default) or invisible. [boolean] + android:maxLines : Makes the TextView be at most this many lines tall. [integer] + android:maxHeight : Makes the TextView be at most this many pixels tall. [dimension] + android:lines : Makes the TextView be exactly this many lines tall. [integer] + android:height : Makes the TextView be exactly this many pixels tall. [dimension] + android:minLines : Makes the TextView be at least this many lines tall. [integer] + android:minHeight : Makes the TextView be at least this many pixels tall. [dimension] + android:maxEms : Makes the TextView be at most this many ems wide. [integer] + android:maxWidth : Makes the TextView be at most this many pixels wide. [dimension] + android:ems : Makes the TextView be exactly this many ems wide. [integer] + android:width : Makes the TextView be exactly this many pixels wide. [dimension] + android:minEms : Makes the TextView be at least this many ems wide. [integer] + android:minWidth : Makes the TextView be at least this many pixels wide. [dimension] + android:gravity : Specifies how to align the text by the view's x- and/or y-axis when the text is smaller than the view. [flag] + android:scrollHorizontally : Whether the text is allowed to be wider than the view (and therefore can be scrolled horizontally). [boolean] + android:password : Whether the characters of the field are displayed as password dots instead of themselves. * Deprecated: Use inputType instead. [boolean] + android:singleLine : Constrains the text to a single horizontally scrolling line instead of letting it wrap onto multiple lines, and advances focus instead of inserting a newline when you press the enter key. * Deprecated: This attribute is deprecated and is replaced by the textMultiLine flag in the inputType attribute. Use caution when altering existing layouts, as the default value of singeLine is false (multi-line mode), but if you specify any value for inputType, the default is single-line mode. (If both singleLine and inputType attributes are found, the inputType flags will override the value of singleLine.). [boolean] + android:enabled : Specifies whether the TextView is enabled or not. * Deprecated: Use state_enabled instead. [boolean] + android:selectAllOnFocus : If the text is selectable, select it all when the view takes focus instead of moving the cursor to the start or end. [boolean] + android:includeFontPadding : Leave enough room for ascenders and descenders instead of using the font ascent and descent strictly. [boolean] + android:maxLength : Set an input filter to constrain the text length to the specified number. [integer] + android:shadowColor : Place a shadow of the specified color behind the text. [color] + android:shadowDx : Horizontal offset of the shadow. [float] + android:shadowDy : Vertical offset of the shadow. [float] + android:shadowRadius : Radius of the shadow. [float] + android:autoLink : Controls whether links such as urls and email addresses are automatically found and converted to clickable links. [flag] + android:linksClickable : If set to false, keeps the movement method from being set to the link movement method even if autoLink causes links to be found. [boolean] + android:numeric : If set, specifies that this TextView has a numeric input method. * Deprecated: Use inputType instead. [flag] + android:digits : If set, specifies that this TextView has a numeric input method and that these specific characters are the ones that it will accept. [string] + android:phoneNumber : If set, specifies that this TextView has a phone number input method. * Deprecated: Use inputType instead. [boolean] + android:inputMethod : If set, specifies that this TextView should use the specified input method (specified by fully-qualified class name). * Deprecated: Use inputType instead. [string] + android:capitalize : If set, specifies that this TextView has a textual input method and should automatically capitalize what the user types. * Deprecated: Use inputType instead. [enum] + android:autoText : If set, specifies that this TextView has a textual input method and automatically corrects some common spelling errors. * Deprecated: Use inputType instead. [boolean] + android:editable : If set, specifies that this TextView has an input method. * Deprecated: Use inputType instead. [boolean] + android:freezesText : If set, the text view will include its current complete text inside of its frozen icicle in addition to meta-data such as the current cursor position. [boolean] + android:ellipsize : If set, causes words that are longer than the view is wide to be ellipsized instead of broken in the middle. [enum] + android:drawableTop : The drawable to be drawn above the text. [reference, color] + android:drawableBottom : The drawable to be drawn below the text. [reference, color] + android:drawableLeft : The drawable to be drawn to the left of the text. [reference, color] + android:drawableRight : The drawable to be drawn to the right of the text. [reference, color] + android:drawablePadding : The padding between the drawables and the text. [dimension] + android:lineSpacingExtra : Extra spacing between lines of text. [dimension] + android:lineSpacingMultiplier : Extra spacing between lines of text, as a multiplier. [float] + android:marqueeRepeatLimit : The number of times to repeat the marquee animation. [integer, enum] + android:inputType : The type of data being placed in a text field, used to help an input method decide how to let the user enter text. [flag] + android:imeOptions : Additional features you can enable in an IME associated with an editor to improve the integration with your application. [flag] + android:privateImeOptions : An addition content type description to supply to the input method attached to the text view, which is private to the implementation of the input method. [string] + android:imeActionLabel : Supply a value for EditorInfo.actionLabel used when an input method is connected to the text view. [string] + android:imeActionId : Supply a value for EditorInfo.actionId used when an input method is connected to the text view. [integer] + android:editorExtras : Reference to an "input-extras" XML resource containing additional data to supply to an input method, which is private to the implementation of the input method. [reference] + android:textSelectHandleLeft : Reference to a drawable that will be used to display a text selection anchor on the left side of a selection region. [reference] + android:textSelectHandleRight : Reference to a drawable that will be used to display a text selection anchor on the right side of a selection region. [reference] + android:textSelectHandle : Reference to a drawable that will be used to display a text selection anchor for positioning the cursor within text. [reference] + android:textEditPasteWindowLayout : The layout of the view that is displayed on top of the cursor to paste inside a TextEdit field. [reference] + android:textEditNoPasteWindowLayout : Variation of textEditPasteWindowLayout displayed when the clipboard is empty. [reference] + android:textEditSidePasteWindowLayout : Used instead of textEditPasteWindowLayout when the window is moved on the side of the insertion cursor because it would be clipped if it were positioned on top. [reference] + android:textEditSideNoPasteWindowLayout : Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. [reference] + android:textCursorDrawable : Reference to a drawable that will be drawn under the insertion cursor. [reference] + android:textIsSelectable : Indicates that the content of a non-editable text can be selected. [boolean] + android:id : Supply an identifier name for this view, to later retrieve it with View.findViewById() or Activity.findViewById(). [reference] + android:tag : Supply a tag for this view containing a String, to be retrieved later with View.getTag() or searched for with View.findViewWithTag() . [string] + android:scrollX : The initial horizontal scroll offset, in pixels. [dimension] + android:scrollY : The initial vertical scroll offset, in pixels. [dimension] + android:background : A drawable to use as the background. [reference, color] + android:padding : Sets the padding, in pixels, of all four edges. [dimension] + android:paddingLeft : Sets the padding, in pixels, of the left edge; see padding. [dimension] + android:paddingTop : Sets the padding, in pixels, of the top edge; see padding. [dimension] + android:paddingRight : Sets the padding, in pixels, of the right edge; see padding. [dimension] + android:paddingBottom : Sets the padding, in pixels, of the bottom edge; see padding. [dimension] + android:focusable : Boolean that controls whether a view can take focus. [boolean] + android:focusableInTouchMode : Boolean that controls whether a view can take focus while in touch mode. [boolean] + android:visibility : Controls the initial visibility of the view. [enum] + android:fitsSystemWindows : Boolean internal attribute to adjust view layout based on system windows such as the status bar. [boolean] + android:scrollbars : Defines which scrollbars should be displayed on scrolling or not. [flag] + android:scrollbarStyle : Controls the scrollbar style and position. [enum] + android:isScrollContainer : Set this if the view will serve as a scrolling container, meaing that it can be resized to shrink its overall window so that there will be space for an input method. [boolean] + android:fadeScrollbars : Defines whether to fade out scrollbars when they are not in use. [boolean] + android:scrollbarFadeDuration : Defines the delay in milliseconds that a scrollbar takes to fade out. [integer] + android:scrollbarDefaultDelayBeforeFade : Defines the delay in milliseconds that a scrollbar waits before fade out. [integer] + android:scrollbarSize : Sets the width of vertical scrollbars and height of horizontal scrollbars. [dimension] + android:scrollbarThumbHorizontal : Defines the horizontal scrollbar thumb drawable. [reference] + android:scrollbarThumbVertical : Defines the vertical scrollbar thumb drawable. [reference] + android:scrollbarTrackHorizontal : Defines the horizontal scrollbar track drawable. [reference] + android:scrollbarTrackVertical : Defines the vertical scrollbar track drawable. [reference] + android:scrollbarAlwaysDrawHorizontalTrack : Defines whether the horizontal scrollbar track should always be drawn. [boolean] + android:scrollbarAlwaysDrawVerticalTrack : Defines whether the vertical scrollbar track should always be drawn. [boolean] + android:fadingEdge : Defines which edges should be fadeded on scrolling. [flag] + android:fadingEdgeLength : Defines the length of the fading edges. [dimension] + android:nextFocusLeft : Defines the next view to give focus to when the next focus is FOCUS_LEFT. [reference] + android:nextFocusRight : Defines the next view to give focus to when the next focus is FOCUS_RIGHT If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:nextFocusUp : Defines the next view to give focus to when the next focus is FOCUS_UP If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:nextFocusDown : Defines the next view to give focus to when the next focus is FOCUS_DOWN If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:nextFocusForward : Defines the next view to give focus to when the next focus is FOCUS_FORWARD If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:clickable : Defines whether this view reacts to click events. [boolean] + android:longClickable : Defines whether this view reacts to long click events. [boolean] + android:saveEnabled : If unset, no state will be saved for this view when it is being frozen. [boolean] + android:filterTouchesWhenObscured : Specifies whether to filter touches when the view's window is obscured by another visible window. [boolean] + android:drawingCacheQuality : Defines the quality of translucent drawing caches. [enum] + android:keepScreenOn : Controls whether the view's window should keep the screen on while visible. [boolean] + android:duplicateParentState : When this attribute is set to true, the view gets its drawable state (focused, pressed, etc.) from its direct parent rather than from itself. [boolean] + android:minHeight : Defines the minimum height of the view. + android:minWidth : Defines the minimum width of the view. + android:soundEffectsEnabled : Boolean that controls whether a view should have sound effects enabled for events such as clicking and touching. [boolean] + android:hapticFeedbackEnabled : Boolean that controls whether a view should have haptic feedback enabled for events such as long presses. [boolean] + android:contentDescription : Defines text that briefly describes content of the view. [string] + android:onClick : Name of the method in this View's context to invoke when the view is clicked. [string] + android:overScrollMode : Defines over-scrolling behavior. [enum] + android:alpha : alpha property of the view, as a value between 0 (completely transparent) and 1 (completely opaque). [float] + android:translationX : translation in x of the view. [dimension] + android:translationY : translation in y of the view. [dimension] + android:transformPivotX : x location of the pivot point around which the view will rotate and scale. [dimension] + android:transformPivotY : y location of the pivot point around which the view will rotate and scale. [dimension] + android:rotation : rotation of the view, in degrees. [float] + android:rotationX : rotation of the view around the x axis, in degrees. [float] + android:rotationY : rotation of the view around the y axis, in degrees. [float] + android:scaleX : scale of the view in the x direction. [float] + android:scaleY : scale of the view in the y direction. [float] + android:verticalScrollbarPosition : Determines which side the vertical scroll bar should be placed on. [enum] + android:layerType : Specifies the type of layer backing this view. [enum] + android:layout_width : Specifies the basic width of the view. [dimension, enum] + android:layout_height : Specifies the basic height of the view. [dimension, enum] + android:layout_weight : [float] + android:layout_gravity : Standard gravity constant that a child can supply to its parent. [flag] + android:layout_margin : Specifies extra space on the left, top, right and bottom sides of this view. [dimension] + android:layout_marginLeft : Specifies extra space on the left side of this view. [dimension] + android:layout_marginTop : Specifies extra space on the top side of this view. [dimension] + android:layout_marginRight : Specifies extra space on the right side of this view. [dimension] + android:layout_marginBottom : Specifies extra space on the bottom side of this view. [dimension] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion4.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion4.txt new file mode 100644 index 0000000..a9111eb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion4.txt @@ -0,0 +1,7 @@ +Code completion in completion1.xml for marginBottom="50^": +50dp : <b>Density-independent Pixels</b> - an abstract unit that is based on the physical density of the screen. +50sp : <b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by the user's font size preference. +50pt : <b>Points</b> - 1/72 of an inch based on the physical size of the screen. +50mm : <b>Millimeters</b> - based on the physical size of the screen. +50in : <b>Inches</b> - based on the physical size of the screen. +50px : <b>Pixels</b> - corresponds to actual pixels on the screen. Not recommended. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion5.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion5.txt new file mode 100644 index 0000000..6b1c993 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion5.txt @@ -0,0 +1,2 @@ +Code completion in completion1.xml for layout_marginLeft="50d^p": +50dp : <b>Density-independent Pixels</b> - an abstract unit that is based on the physical density of the screen. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt new file mode 100644 index 0000000..0853a83 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion6.txt @@ -0,0 +1,22 @@ +Code completion in completion1.xml for style="@android:^style/Widget.Button": +@android:style/ +@android:anim/ +@android:animator/ +@android:array/ +@android:bool/ +@android:color/ +@android:declare-styleable/ +@android:dimen/ +@android:drawable/ +@android:fraction/ +@android:id/ +@android:integer/ +@android:interpolator/ +@android:layout/ +@android:menu/ +@android:mipmap/ +@android:plurals/ +@android:public/ +@android:raw/ +@android:string/ +@android:xml/ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion7a.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion7a.txt new file mode 100644 index 0000000..cf373ad --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion7a.txt @@ -0,0 +1,2 @@ +Code completion in completion1.xml for android:gravity="l^eft|bottom": +left diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion7b.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion7b.txt new file mode 100644 index 0000000..66276d6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion7b.txt @@ -0,0 +1,2 @@ +Code completion in completion1.xml for android:gravity="left|b^ottom": +bottom diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion8.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion8.txt new file mode 100644 index 0000000..bee7f93 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion8.txt @@ -0,0 +1,2 @@ +Code completion in completion1.xml for layout_width^="fill_parent": +android:layout_width : Specifies the basic width of the view. [dimension, enum] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion9.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion9.txt new file mode 100644 index 0000000..ab6a0d2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion9.txt @@ -0,0 +1,4 @@ +Code completion in completion1.xml for layout_width=^"fill_parent": +"fill_parent" +"match_parent" +"wrap_content" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1.xml new file mode 100644 index 0000000..d523eeb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> +<!-- + This file deliberately contains errors - it represents partial keyboard + typing for interactive code completion +--> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@android:dimen/app_icon_size" + android:layout_marginLeft="50dp" + android:layout_marginBottom="50" + android:textColor="#000000" + style="@android:style/Widget.Button" + android:gravity="left|bottom" + android:text="@string/hello" + android:hint="hint" /> + <FrameLayout android:foreground="@android:drawable/btn_default"></FrameLayout> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13a.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13a.diff new file mode 100644 index 0000000..13e224e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13a.diff @@ -0,0 +1,4 @@ +Code completion in completion2.xml for gravity="left|bottom|^cen selecting fill_vertical: +< <TextView android:gravity="left|bottom|^cen"></TextView> +--- +> <TextView android:gravity="left|bottom|fill_vertical^"></TextView> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13b.diff new file mode 100644 index 0000000..5d1b39e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13b.diff @@ -0,0 +1,4 @@ +Code completion in completion2.xml for gravity="left|bottom|cen^ selecting center_horizontal: +< <TextView android:gravity="left|bottom|cen^"></TextView> +--- +> <TextView android:gravity="left|bottom|center_horizontal^"></TextView> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13c.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13c.diff new file mode 100644 index 0000000..0c7e564 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-applyCompletion13c.diff @@ -0,0 +1,4 @@ +Code completion in completion2.xml for gravity="left|bottom^|cen selecting bottom|fill_horizontal: +< <TextView android:gravity="left|bottom^|cen"></TextView> +--- +> <TextView android:gravity="left|bottom|fill_horizontal^"></TextView> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13a.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13a.txt new file mode 100644 index 0000000..323bb78 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13a.txt @@ -0,0 +1,13 @@ +Code completion in completion2.xml for gravity="left|bottom|^cen: +top +bottom +left +right +center_vertical +fill_vertical +center_horizontal +fill_horizontal +center +fill +clip_vertical +clip_horizontal diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13b.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13b.txt new file mode 100644 index 0000000..8e6d4d8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13b.txt @@ -0,0 +1,4 @@ +Code completion in completion2.xml for gravity="left|bottom|cen^: +center_vertical +center_horizontal +center diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13c.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13c.txt new file mode 100644 index 0000000..3e292ce --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2-expected-completion13c.txt @@ -0,0 +1,12 @@ +Code completion in completion2.xml for gravity="left|bottom^|cen: +bottom +bottom|top +bottom|right +bottom|center_vertical +bottom|fill_vertical +bottom|center_horizontal +bottom|fill_horizontal +bottom|center +bottom|fill +bottom|clip_vertical +bottom|clip_horizontal diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2.xml new file mode 100644 index 0000000..0921674 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion2.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Test multiple pipes in the flag value --> + <TextView android:gravity="left|bottom|cen"></TextView> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3-expected-applyCompletion17.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3-expected-applyCompletion17.diff new file mode 100644 index 0000000..14bb9ac --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3-expected-applyCompletion17.diff @@ -0,0 +1,4 @@ +Code completion in completion3.xml for <EditText ^/> selecting android:textColorHighlight: +< <EditText ^/> +--- +> <EditText android:textColorHighlight="^"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3-expected-applyCompletion18.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3-expected-applyCompletion18.diff new file mode 100644 index 0000000..a751a3e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3-expected-applyCompletion18.diff @@ -0,0 +1,4 @@ +Code completion in completion3.xml for <Button ^></Button> selecting android:paddingRight: +< <Button ^></Button> +--- +> <Button android:paddingRight="^"></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3.xml new file mode 100644 index 0000000..afb64b8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion3.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> + <EditText /> + <Button ></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion4-expected-completion22.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion4-expected-completion22.txt new file mode 100644 index 0000000..c7170df --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion4-expected-completion22.txt @@ -0,0 +1,2 @@ +Code completion in completion4.xml for <Button^: +Button diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion4.xml new file mode 100644 index 0000000..0047772 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion4.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:text="@string/app_name" + android:layout_marginLeft="@android:dimen/app_icon_size" + android:id="@+id/button1" + ></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion19.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion19.diff new file mode 100644 index 0000000..16f42b2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion19.diff @@ -0,0 +1,4 @@ +Code completion in completion5.xml for android:orientation='^' selecting horizontal: +< android:orientation='^' +--- +> android:orientation='horizontal'^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion20.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion20.diff new file mode 100644 index 0000000..dd48af1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion20.diff @@ -0,0 +1,4 @@ +Code completion in completion5.xml for android:layout_marginTop='50^dp' selecting 50pt: +< android:layout_marginTop='50^dp' +--- +> android:layout_marginTop='50pt'^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion21.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion21.diff new file mode 100644 index 0000000..40e5bb1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion21.diff @@ -0,0 +1,4 @@ +Code completion in completion5.xml for android:layout_width='^wrap_content' selecting match_parent: +< android:layout_width='^wrap_content' +--- +> android:layout_width='match_parent'^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion40.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion40.diff new file mode 100644 index 0000000..00ffd0a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-applyCompletion40.diff @@ -0,0 +1,4 @@ +Code completion in completion5.xml for android:id='@+id/button2'^ selecting android:maxWidth: +< android:id='@+id/button2'^ +--- +> android:id='@+id/button2' android:maxWidth="^" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt new file mode 100644 index 0000000..0b5aa7e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt @@ -0,0 +1,142 @@ +Code completion in completion5.xml for android:id='@+id/button2'^: + style : A reference to a custom style [reference] + android:bufferType : Determines the minimum type that getText() will return. [enum] + android:text : Text to display. [string] + android:hint : Hint text to display when the text is empty. [string] + android:textColor : Text color. [reference, color] + android:textColorHighlight : Color of the text selection highlight. [reference, color] + android:textColorHint : Color of the hint text. [reference, color] + android:textAppearance : Base text color, typeface, size, and style. [reference] + android:textSize : Size of the text. [dimension] + android:textScaleX : Sets the horizontal scaling factor for the text. [float] + android:typeface : Typeface (normal, sans, serif, monospace) for the text. [enum] + android:textStyle : Style (bold, italic, bolditalic) for the text. [flag] + android:textColorLink : Text color for links. [reference, color] + android:cursorVisible : Makes the cursor visible (the default) or invisible. [boolean] + android:maxLines : Makes the TextView be at most this many lines tall. [integer] + android:maxHeight : Makes the TextView be at most this many pixels tall. [dimension] + android:lines : Makes the TextView be exactly this many lines tall. [integer] + android:height : Makes the TextView be exactly this many pixels tall. [dimension] + android:minLines : Makes the TextView be at least this many lines tall. [integer] + android:minHeight : Makes the TextView be at least this many pixels tall. [dimension] + android:maxEms : Makes the TextView be at most this many ems wide. [integer] + android:maxWidth : Makes the TextView be at most this many pixels wide. [dimension] + android:ems : Makes the TextView be exactly this many ems wide. [integer] + android:width : Makes the TextView be exactly this many pixels wide. [dimension] + android:minEms : Makes the TextView be at least this many ems wide. [integer] + android:minWidth : Makes the TextView be at least this many pixels wide. [dimension] + android:gravity : Specifies how to align the text by the view's x- and/or y-axis when the text is smaller than the view. [flag] + android:scrollHorizontally : Whether the text is allowed to be wider than the view (and therefore can be scrolled horizontally). [boolean] + android:password : Whether the characters of the field are displayed as password dots instead of themselves. * Deprecated: Use inputType instead. [boolean] + android:singleLine : Constrains the text to a single horizontally scrolling line instead of letting it wrap onto multiple lines, and advances focus instead of inserting a newline when you press the enter key. * Deprecated: This attribute is deprecated and is replaced by the textMultiLine flag in the inputType attribute. Use caution when altering existing layouts, as the default value of singeLine is false (multi-line mode), but if you specify any value for inputType, the default is single-line mode. (If both singleLine and inputType attributes are found, the inputType flags will override the value of singleLine.). [boolean] + android:enabled : Specifies whether the TextView is enabled or not. * Deprecated: Use state_enabled instead. [boolean] + android:selectAllOnFocus : If the text is selectable, select it all when the view takes focus instead of moving the cursor to the start or end. [boolean] + android:includeFontPadding : Leave enough room for ascenders and descenders instead of using the font ascent and descent strictly. [boolean] + android:maxLength : Set an input filter to constrain the text length to the specified number. [integer] + android:shadowColor : Place a shadow of the specified color behind the text. [color] + android:shadowDx : Horizontal offset of the shadow. [float] + android:shadowDy : Vertical offset of the shadow. [float] + android:shadowRadius : Radius of the shadow. [float] + android:autoLink : Controls whether links such as urls and email addresses are automatically found and converted to clickable links. [flag] + android:linksClickable : If set to false, keeps the movement method from being set to the link movement method even if autoLink causes links to be found. [boolean] + android:numeric : If set, specifies that this TextView has a numeric input method. * Deprecated: Use inputType instead. [flag] + android:digits : If set, specifies that this TextView has a numeric input method and that these specific characters are the ones that it will accept. [string] + android:phoneNumber : If set, specifies that this TextView has a phone number input method. * Deprecated: Use inputType instead. [boolean] + android:inputMethod : If set, specifies that this TextView should use the specified input method (specified by fully-qualified class name). * Deprecated: Use inputType instead. [string] + android:capitalize : If set, specifies that this TextView has a textual input method and should automatically capitalize what the user types. * Deprecated: Use inputType instead. [enum] + android:autoText : If set, specifies that this TextView has a textual input method and automatically corrects some common spelling errors. * Deprecated: Use inputType instead. [boolean] + android:editable : If set, specifies that this TextView has an input method. * Deprecated: Use inputType instead. [boolean] + android:freezesText : If set, the text view will include its current complete text inside of its frozen icicle in addition to meta-data such as the current cursor position. [boolean] + android:ellipsize : If set, causes words that are longer than the view is wide to be ellipsized instead of broken in the middle. [enum] + android:drawableTop : The drawable to be drawn above the text. [reference, color] + android:drawableBottom : The drawable to be drawn below the text. [reference, color] + android:drawableLeft : The drawable to be drawn to the left of the text. [reference, color] + android:drawableRight : The drawable to be drawn to the right of the text. [reference, color] + android:drawablePadding : The padding between the drawables and the text. [dimension] + android:lineSpacingExtra : Extra spacing between lines of text. [dimension] + android:lineSpacingMultiplier : Extra spacing between lines of text, as a multiplier. [float] + android:marqueeRepeatLimit : The number of times to repeat the marquee animation. [integer, enum] + android:inputType : The type of data being placed in a text field, used to help an input method decide how to let the user enter text. [flag] + android:imeOptions : Additional features you can enable in an IME associated with an editor to improve the integration with your application. [flag] + android:privateImeOptions : An addition content type description to supply to the input method attached to the text view, which is private to the implementation of the input method. [string] + android:imeActionLabel : Supply a value for EditorInfo.actionLabel used when an input method is connected to the text view. [string] + android:imeActionId : Supply a value for EditorInfo.actionId used when an input method is connected to the text view. [integer] + android:editorExtras : Reference to an "input-extras" XML resource containing additional data to supply to an input method, which is private to the implementation of the input method. [reference] + android:textSelectHandleLeft : Reference to a drawable that will be used to display a text selection anchor on the left side of a selection region. [reference] + android:textSelectHandleRight : Reference to a drawable that will be used to display a text selection anchor on the right side of a selection region. [reference] + android:textSelectHandle : Reference to a drawable that will be used to display a text selection anchor for positioning the cursor within text. [reference] + android:textEditPasteWindowLayout : The layout of the view that is displayed on top of the cursor to paste inside a TextEdit field. [reference] + android:textEditNoPasteWindowLayout : Variation of textEditPasteWindowLayout displayed when the clipboard is empty. [reference] + android:textEditSidePasteWindowLayout : Used instead of textEditPasteWindowLayout when the window is moved on the side of the insertion cursor because it would be clipped if it were positioned on top. [reference] + android:textEditSideNoPasteWindowLayout : Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. [reference] + android:textCursorDrawable : Reference to a drawable that will be drawn under the insertion cursor. [reference] + android:textIsSelectable : Indicates that the content of a non-editable text can be selected. [boolean] + android:id : Supply an identifier name for this view, to later retrieve it with View.findViewById() or Activity.findViewById(). [reference] + android:tag : Supply a tag for this view containing a String, to be retrieved later with View.getTag() or searched for with View.findViewWithTag() . [string] + android:scrollX : The initial horizontal scroll offset, in pixels. [dimension] + android:scrollY : The initial vertical scroll offset, in pixels. [dimension] + android:background : A drawable to use as the background. [reference, color] + android:padding : Sets the padding, in pixels, of all four edges. [dimension] + android:paddingLeft : Sets the padding, in pixels, of the left edge; see padding. [dimension] + android:paddingTop : Sets the padding, in pixels, of the top edge; see padding. [dimension] + android:paddingRight : Sets the padding, in pixels, of the right edge; see padding. [dimension] + android:paddingBottom : Sets the padding, in pixels, of the bottom edge; see padding. [dimension] + android:focusable : Boolean that controls whether a view can take focus. [boolean] + android:focusableInTouchMode : Boolean that controls whether a view can take focus while in touch mode. [boolean] + android:visibility : Controls the initial visibility of the view. [enum] + android:fitsSystemWindows : Boolean internal attribute to adjust view layout based on system windows such as the status bar. [boolean] + android:scrollbars : Defines which scrollbars should be displayed on scrolling or not. [flag] + android:scrollbarStyle : Controls the scrollbar style and position. [enum] + android:isScrollContainer : Set this if the view will serve as a scrolling container, meaing that it can be resized to shrink its overall window so that there will be space for an input method. [boolean] + android:fadeScrollbars : Defines whether to fade out scrollbars when they are not in use. [boolean] + android:scrollbarFadeDuration : Defines the delay in milliseconds that a scrollbar takes to fade out. [integer] + android:scrollbarDefaultDelayBeforeFade : Defines the delay in milliseconds that a scrollbar waits before fade out. [integer] + android:scrollbarSize : Sets the width of vertical scrollbars and height of horizontal scrollbars. [dimension] + android:scrollbarThumbHorizontal : Defines the horizontal scrollbar thumb drawable. [reference] + android:scrollbarThumbVertical : Defines the vertical scrollbar thumb drawable. [reference] + android:scrollbarTrackHorizontal : Defines the horizontal scrollbar track drawable. [reference] + android:scrollbarTrackVertical : Defines the vertical scrollbar track drawable. [reference] + android:scrollbarAlwaysDrawHorizontalTrack : Defines whether the horizontal scrollbar track should always be drawn. [boolean] + android:scrollbarAlwaysDrawVerticalTrack : Defines whether the vertical scrollbar track should always be drawn. [boolean] + android:fadingEdge : Defines which edges should be fadeded on scrolling. [flag] + android:fadingEdgeLength : Defines the length of the fading edges. [dimension] + android:nextFocusLeft : Defines the next view to give focus to when the next focus is FOCUS_LEFT. [reference] + android:nextFocusRight : Defines the next view to give focus to when the next focus is FOCUS_RIGHT If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:nextFocusUp : Defines the next view to give focus to when the next focus is FOCUS_UP If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:nextFocusDown : Defines the next view to give focus to when the next focus is FOCUS_DOWN If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:nextFocusForward : Defines the next view to give focus to when the next focus is FOCUS_FORWARD If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] + android:clickable : Defines whether this view reacts to click events. [boolean] + android:longClickable : Defines whether this view reacts to long click events. [boolean] + android:saveEnabled : If unset, no state will be saved for this view when it is being frozen. [boolean] + android:filterTouchesWhenObscured : Specifies whether to filter touches when the view's window is obscured by another visible window. [boolean] + android:drawingCacheQuality : Defines the quality of translucent drawing caches. [enum] + android:keepScreenOn : Controls whether the view's window should keep the screen on while visible. [boolean] + android:duplicateParentState : When this attribute is set to true, the view gets its drawable state (focused, pressed, etc.) from its direct parent rather than from itself. [boolean] + android:minHeight : Defines the minimum height of the view. + android:minWidth : Defines the minimum width of the view. + android:soundEffectsEnabled : Boolean that controls whether a view should have sound effects enabled for events such as clicking and touching. [boolean] + android:hapticFeedbackEnabled : Boolean that controls whether a view should have haptic feedback enabled for events such as long presses. [boolean] + android:contentDescription : Defines text that briefly describes content of the view. [string] + android:onClick : Name of the method in this View's context to invoke when the view is clicked. [string] + android:overScrollMode : Defines over-scrolling behavior. [enum] + android:alpha : alpha property of the view, as a value between 0 (completely transparent) and 1 (completely opaque). [float] + android:translationX : translation in x of the view. [dimension] + android:translationY : translation in y of the view. [dimension] + android:transformPivotX : x location of the pivot point around which the view will rotate and scale. [dimension] + android:transformPivotY : y location of the pivot point around which the view will rotate and scale. [dimension] + android:rotation : rotation of the view, in degrees. [float] + android:rotationX : rotation of the view around the x axis, in degrees. [float] + android:rotationY : rotation of the view around the y axis, in degrees. [float] + android:scaleX : scale of the view in the x direction. [float] + android:scaleY : scale of the view in the y direction. [float] + android:verticalScrollbarPosition : Determines which side the vertical scroll bar should be placed on. [enum] + android:layerType : Specifies the type of layer backing this view. [enum] + android:layout_width : Specifies the basic width of the view. [dimension, enum] + android:layout_height : Specifies the basic height of the view. [dimension, enum] + android:layout_weight : [float] + android:layout_gravity : Standard gravity constant that a child can supply to its parent. [flag] + android:layout_margin : Specifies extra space on the left, top, right and bottom sides of this view. [dimension] + android:layout_marginLeft : Specifies extra space on the left side of this view. [dimension] + android:layout_marginTop : Specifies extra space on the top side of this view. [dimension] + android:layout_marginRight : Specifies extra space on the right side of this view. [dimension] + android:layout_marginBottom : Specifies extra space on the bottom side of this view. [dimension] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5.xml new file mode 100644 index 0000000..757be8b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5.xml @@ -0,0 +1,23 @@ +<?xml version='1.0' encoding='utf-8'?> +<LinearLayout + xmlns:android='http://schemas.android.com/apk/res/android' + android:layout_width='match_parent' + android:orientation='' + android:layout_height='match_parent'> + <Button + android:text="what's up doc?" + android:id='@+id/button1' + android:layout_marginTop='50dp' + android:layout_width='wrap_content' + android:layout_height='wrap_content'></Button> + <Button + android:text="quote='" + android:id='@+id/button2' + android:layout_width='wrap_content' + android:layout_height='wrap_content'></Button> + <Button + android:text='quote="' + android:id='@+id/button2' + android:layout_width='wrap_content' + android:layout_height='wrap_content'></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion6-expected-applyCompletion22.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion6-expected-applyCompletion22.diff new file mode 100644 index 0000000..8ea8837 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion6-expected-applyCompletion22.diff @@ -0,0 +1,4 @@ +Code completion in completion6.xml for android:orientation="^" selecting horizontal: +< android:orientation="^" +--- +> android:orientation="horizontal"^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion6.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion6.xml new file mode 100644 index 0000000..55bfa0c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion6.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<LinearLayout + xmlns:android='http://schemas.android.com/apk/res/android' + android:layout_width='match_parent' + android:orientation="" + android:layout_height='match_parent'> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion7-expected-applyCompletion23.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion7-expected-applyCompletion23.diff new file mode 100644 index 0000000..d874fc1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion7-expected-applyCompletion23.diff @@ -0,0 +1,4 @@ +Code completion in completion7.xml for android:orientation="^ selecting horizontal: +< android:orientation="^ +--- +> android:orientation="horizontal^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion7.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion7.xml new file mode 100644 index 0000000..30afbaf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion7.xml @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='utf-8'?> +<LinearLayout + xmlns:android='http://schemas.android.com/apk/res/android' + android:layout_width='match_parent' + android:orientation=" + android:layout_height='match_parent'> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-applyCompletion41.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-applyCompletion41.diff new file mode 100644 index 0000000..9a1b962 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-applyCompletion41.diff @@ -0,0 +1,4 @@ +Code completion in completion8.xml for android:mar^="50dp" selecting android:layout_marginRight: +< android:mar^="50dp" +--- +> android:layout_marginRight^="50dp" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion41.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion41.txt new file mode 100644 index 0000000..917d888 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion41.txt @@ -0,0 +1,7 @@ +Code completion in completion8.xml for android:mar^="50dp": +android:marqueeRepeatLimit : The number of times to repeat the marquee animation. [integer, enum] +android:layout_margin : Specifies extra space on the left, top, right and bottom sides of this view. [dimension] +android:layout_marginLeft : Specifies extra space on the left side of this view. [dimension] +android:layout_marginTop : Specifies extra space on the top side of this view. [dimension] +android:layout_marginRight : Specifies extra space on the right side of this view. [dimension] +android:layout_marginBottom : Specifies extra space on the bottom side of this view. [dimension] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion42.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion42.txt new file mode 100644 index 0000000..f51744d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion42.txt @@ -0,0 +1,4 @@ +Code completion in completion8.xml for android:w^i="100": +android:width : Makes the TextView be exactly this many pixels wide. [dimension] +android:layout_width : Specifies the basic width of the view. [dimension, enum] +android:layout_weight : [float] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion43.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion43.txt new file mode 100644 index 0000000..622a2ba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion43.txt @@ -0,0 +1,7 @@ +Code completion in completion8.xml for mar^="60dp": +android:marqueeRepeatLimit : The number of times to repeat the marquee animation. [integer, enum] +android:layout_margin : Specifies extra space on the left, top, right and bottom sides of this view. [dimension] +android:layout_marginLeft : Specifies extra space on the left side of this view. [dimension] +android:layout_marginTop : Specifies extra space on the top side of this view. [dimension] +android:layout_marginRight : Specifies extra space on the right side of this view. [dimension] +android:layout_marginBottom : Specifies extra space on the bottom side of this view. [dimension] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion44.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion44.txt new file mode 100644 index 0000000..6f7f2d3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8-expected-completion44.txt @@ -0,0 +1,10 @@ +Code completion in completion8.xml for android:layo^ut_width="fill_parent": +android:layout_width : Specifies the basic width of the view. [dimension, enum] +android:layout_height : Specifies the basic height of the view. [dimension, enum] +android:layout_weight : [float] +android:layout_gravity : Standard gravity constant that a child can supply to its parent. [flag] +android:layout_margin : Specifies extra space on the left, top, right and bottom sides of this view. [dimension] +android:layout_marginLeft : Specifies extra space on the left side of this view. [dimension] +android:layout_marginTop : Specifies extra space on the top side of this view. [dimension] +android:layout_marginRight : Specifies extra space on the right side of this view. [dimension] +android:layout_marginBottom : Specifies extra space on the bottom side of this view. [dimension] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8.xml new file mode 100644 index 0000000..3f9d6b5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion8.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:mar="50dp" + android:wi="100" + mar="60dp" + /> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion24a.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion24a.diff new file mode 100644 index 0000000..4b88448 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion24a.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for android:textS^ize selecting android:textSelectHandleLeft: +< <item name="android:textS^ize">17sp</item> +--- +> <item name="android:textSelectHandleLeft"^>17sp</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion24b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion24b.diff new file mode 100644 index 0000000..acfd3ab --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion24b.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for 17^sp selecting 17mm: +< <item name="android:textSize">17^sp</item> +--- +> <item name="android:textSize">17mm^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion25.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion25.diff new file mode 100644 index 0000000..ba7a1f2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion25.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for textColor">^@color/title_color</item> selecting @android:: +< <item name="android:textColor">^@color/title_color</item> +--- +> <item name="android:textColor">@android:^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion26.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion26.diff new file mode 100644 index 0000000..491363b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion26.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:shadowColor">@an^</item> selecting @android:: +< <item name="android:shadowColor">@an^</item> +--- +> <item name="android:shadowColor">@android:^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion27.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion27.diff new file mode 100644 index 0000000..1c999c7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion27.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:gravity">^ </item> selecting center_vertical: +< <item name="android:gravity">^ </item> +--- +> <item name="android:gravity">center_vertical^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion28.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion28.diff new file mode 100644 index 0000000..fbfa7a8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion28.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:gravity"> ^</item> selecting left: +< <item name="android:gravity"> ^</item> +--- +> <item name="android:gravity"> left^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion29.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion29.diff new file mode 100644 index 0000000..3e2cf0d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion29.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="gr^"> selecting android:gravity: +< <item name="gr^">@color/title_color</item> +--- +> <item name="android:gravity"^>@color/title_color</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion30.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion30.diff new file mode 100644 index 0000000..5f79eaf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion30.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="an^"> selecting android:animateOnClick: +< <item name="an^">@color/title_color</item> +--- +> <item name="android:animateOnClick"^>@color/title_color</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion31.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion31.diff new file mode 100644 index 0000000..8f83183 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion31.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item ^></item> selecting name: +< <item ^></item> +--- +> <item name="^"></item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion32.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion32.diff new file mode 100644 index 0000000..1a68f13 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion32.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="^"></item> selecting android:background: +< <item name="^"></item> +--- +> <item name="android:background"^></item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion33.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion33.diff new file mode 100644 index 0000000..1a61da3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion33.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:allowSingleTap">^</item> selecting true: +< <item name="android:allowSingleTap">^</item> +--- +> <item name="android:allowSingleTap">true^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion34.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion34.diff new file mode 100644 index 0000000..da4d30f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion34.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:alwaysDrawnWithCache">^ false </item> selecting true: +< <item name="android:alwaysDrawnWithCache">^ false </item> +--- +> <item name="android:alwaysDrawnWithCache">true^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion35.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion35.diff new file mode 100644 index 0000000..4334c9f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion35.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:alwaysDrawnWithCache"> ^false </item> selecting true: +< <item name="android:alwaysDrawnWithCache"> ^false </item> +--- +> <item name="android:alwaysDrawnWithCache"> true^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion36.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion36.diff new file mode 100644 index 0000000..2c9f547 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion36.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:alwaysDrawnWithCache"> f^alse </item> selecting false: +< <item name="android:alwaysDrawnWithCache"> f^alse </item> +--- +> <item name="android:alwaysDrawnWithCache"> false^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion37.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion37.diff new file mode 100644 index 0000000..195e159 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion37.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for <item name="android:orientation">h^</item> selecting horizontal: +< <item name="android:orientation">h^</item> +--- +> <item name="android:orientation">horizontal^</item> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion38.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion38.diff new file mode 100644 index 0000000..0e81810 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-applyCompletion38.diff @@ -0,0 +1,4 @@ +Code completion in completionvalues1.xml for c^ selecting center: +< c^ +--- +> center^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion23.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion23.txt new file mode 100644 index 0000000..28cc9ba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion23.txt @@ -0,0 +1,7 @@ +Code completion in completionvalues1.xml for android:textS^ize: +android:textScaleX : Sets the horizontal scaling factor for the text. [float] +android:textSelectHandle : Reference to a drawable that will be used to display a text selection anchor for positioning the cursor within text. [reference] +android:textSelectHandleLeft : Reference to a drawable that will be used to display a text selection anchor on the left side of a selection region. [reference] +android:textSelectHandleRight : Reference to a drawable that will be used to display a text selection anchor on the right side of a selection region. [reference] +android:textSize : Size of the text. [dimension] +android:textStyle : Style (bold, italic, bolditalic) for the text. [flag] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion24.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion24.txt new file mode 100644 index 0000000..c1de9ec --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion24.txt @@ -0,0 +1,7 @@ +Code completion in completionvalues1.xml for 17^sp: +17dp : <b>Density-independent Pixels</b> - an abstract unit that is based on the physical density of the screen. +17sp : <b>Scale-independent Pixels</b> - this is like the dp unit, but it is also scaled by the user's font size preference. +17pt : <b>Points</b> - 1/72 of an inch based on the physical size of the screen. +17mm : <b>Millimeters</b> - based on the physical size of the screen. +17in : <b>Inches</b> - based on the physical size of the screen. +17px : <b>Pixels</b> - corresponds to actual pixels on the screen. Not recommended. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion25.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion25.txt new file mode 100644 index 0000000..06f5a99 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion25.txt @@ -0,0 +1,6 @@ +Code completion in completionvalues1.xml for textColor">^@color/title_color</item>: +@android: +@drawable/ +@layout/ +@string/ +@style/ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion26.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion26.txt new file mode 100644 index 0000000..b1c541c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion26.txt @@ -0,0 +1,2 @@ +Code completion in completionvalues1.xml for <item name="android:shadowColor">@an^</item>: +@android: diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion27.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion27.txt new file mode 100644 index 0000000..5c870fe --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion27.txt @@ -0,0 +1,13 @@ +Code completion in completionvalues1.xml for <item name="android:gravity">^ </item>: +top +bottom +left +right +center_vertical +fill_vertical +center_horizontal +fill_horizontal +center +fill +clip_vertical +clip_horizontal diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion28.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion28.txt new file mode 100644 index 0000000..183a7aa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion28.txt @@ -0,0 +1,13 @@ +Code completion in completionvalues1.xml for <item name="android:gravity"> ^</item>: +top +bottom +left +right +center_vertical +fill_vertical +center_horizontal +fill_horizontal +center +fill +clip_vertical +clip_horizontal diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion29.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion29.txt new file mode 100644 index 0000000..0ad8ad2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion29.txt @@ -0,0 +1,3 @@ +Code completion in completionvalues1.xml for <item name="gr^">: +android:gravity : Specifies how to place the content of an object, both on the x- and y-axis, within the object itself. [flag] +android:groupIndicator : Indicator shown beside the group View. [reference] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion30.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion30.txt new file mode 100644 index 0000000..1f18826 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion30.txt @@ -0,0 +1,8 @@ +Code completion in completionvalues1.xml for <item name="an^">: +android: +android:animateFirstView : Defines whether to animate the current View when the ViewAnimation is first displayed. [boolean] +android:animateLayoutChanges : Defines whether changes in layout (caused by adding and removing items) should cause a LayoutTransition to run. [boolean] +android:animateOnClick : Indicates whether the drawer should be opened/closed with an animation when the user clicks the handle. [boolean] +android:animationCache : Defines whether layout animations should create a drawing cache for their children. [boolean] +android:animationDuration : Sets how long a transition animation should run (in milliseconds) when layout has changed. [integer] +android:animationResolution : Timeout between frames of animation in milliseconds [integer] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion31.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion31.txt new file mode 100644 index 0000000..9044164 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion31.txt @@ -0,0 +1,2 @@ +Code completion in completionvalues1.xml for <item ^></item>: +name : The mandatory name used in referring to this item. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt new file mode 100644 index 0000000..0ca6e41 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt @@ -0,0 +1,293 @@ +Code completion in completionvalues1.xml for <item name="^"></item>: +android: +android:addStatesFromChildren : Sets whether this ViewGroup's drawable states also include its children's drawable states. [boolean] +android:adjustViewBounds : Set this to true if you want the ImageView to adjust its bounds to preserve the aspect ratio of its drawable. [boolean] +android:allowSingleTap : Indicates whether the drawer can be opened/closed by a single tap on the handle. [boolean] +android:alpha : alpha property of the view, as a value between 0 (completely transparent) and 1 (completely opaque). [float] +android:alwaysDrawnWithCache : Defines whether the ViewGroup should always draw its children using their drawing cache or not. [boolean] +android:animateFirstView : Defines whether to animate the current View when the ViewAnimation is first displayed. [boolean] +android:animateLayoutChanges : Defines whether changes in layout (caused by adding and removing items) should cause a LayoutTransition to run. [boolean] +android:animateOnClick : Indicates whether the drawer should be opened/closed with an animation when the user clicks the handle. [boolean] +android:animationCache : Defines whether layout animations should create a drawing cache for their children. [boolean] +android:animationDuration : Sets how long a transition animation should run (in milliseconds) when layout has changed. [integer] +android:animationResolution : Timeout between frames of animation in milliseconds [integer] +android:autoLink : Controls whether links such as urls and email addresses are automatically found and converted to clickable links. [flag] +android:autoStart : When true, automatically start animating [boolean] +android:autoText : If set, specifies that this TextView has a textual input method and automatically corrects some common spelling errors. * Deprecated: Use inputType instead. [boolean] +android:background : A drawable to use as the background. [reference, color] +android:baseline : The offset of the baseline within this view. [dimension] +android:baselineAlignBottom : If true, the image view will be baseline aligned with based on its bottom edge. [boolean] +android:baselineAligned : When set to false, prevents the layout from aligning its children's baselines. [boolean] +android:baselineAlignedChildIndex : When a linear layout is part of another layout that is baseline aligned, it can specify which of its children to baseline align to (that is, which child TextView). [integer] +android:bottomOffset : Extra offset for the handle at the bottom of the SlidingDrawer. [dimension] +android:bufferType : Determines the minimum type that getText() will return. [enum] +android:button : Drawable used for the button graphic (e.g. checkbox, radio button, etc). [reference] +android:cacheColorHint : Indicates that this list will always be drawn on top of solid, single-color opaque background. [color] +android:calendarViewShown : Whether the calendar view is shown. [boolean] +android:capitalize : If set, specifies that this TextView has a textual input method and should automatically capitalize what the user types. * Deprecated: Use inputType instead. [enum] +android:checkMark : Drawable used for the check mark graphic. [reference] +android:checked : Indicates the initial checked state of this button. [boolean] +android:checkedButton : The id of the child radio button that should be checked by default within this radio group. [integer] +android:childDivider : Drawable or color that is used as a divider for children. [reference, color] +android:childIndicator : Indicator shown beside the child View. [reference] +android:childIndicatorLeft : The left bound for a child's indicator. [dimension] +android:childIndicatorRight : The right bound for a child's indicator. [dimension] +android:choiceMode : Defines the choice behavior for the view. [enum] +android:clickable : Defines whether this view reacts to click events. [boolean] +android:clipChildren : Defines whether a child is limited to draw inside of its bounds or not. [boolean] +android:clipToPadding : Defines whether the ViewGroup will clip its drawing surface so as to exclude the padding area. [boolean] +android:collapseColumns : The zero-based index of the columns to collapse. [string] +android:columnWidth : Specifies the fixed width for each column. [dimension] +android:completionHint : Defines the hint displayed in the drop down menu. [string] +android:completionHintView : Defines the hint view displayed in the drop down menu. [reference] +android:completionThreshold : Defines the number of characters that the user must type before completion suggestions are displayed in a drop down menu. [integer] +android:content : Identifier for the child that represents the drawer's content. [reference] +android:contentDescription : Defines text that briefly describes content of the view. [string] +android:cropToPadding : If true, the image will be cropped to fit within its padding. [boolean] +android:cursorVisible : Makes the cursor visible (the default) or invisible. [boolean] +android:dateTextAppearance : The text appearance for the calendar dates. [reference] +android:descendantFocusability : Defines the relationship between the ViewGroup and its descendants when looking for a View to take focus. [enum] +android:dial : [reference] +android:digits : If set, specifies that this TextView has a numeric input method and that these specific characters are the ones that it will accept. [string] +android:disabledAlpha : The alpha to apply to the indicator when disabled. [float] +android:divider : Drawable to use as a vertical divider between buttons. +android:dividerHeight : Height of the divider. [dimension] +android:dividerPadding : Size of padding on either end of a divider. [dimension] +android:drawSelectorOnTop : When set to true, the selector will be drawn over the selected item. [boolean] +android:drawableBottom : The drawable to be drawn below the text. [reference, color] +android:drawableLeft : The drawable to be drawn to the left of the text. [reference, color] +android:drawablePadding : The padding between the drawables and the text. [dimension] +android:drawableRight : The drawable to be drawn to the right of the text. [reference, color] +android:drawableTop : The drawable to be drawn above the text. [reference, color] +android:drawingCacheQuality : Defines the quality of translucent drawing caches. [enum] +android:dropDownAnchor : View to anchor the auto-complete dropdown to. [reference] +android:dropDownHeight : Specifies the basic height of the dropdown. [dimension, enum] +android:dropDownHorizontalOffset : Horizontal offset from the spinner widget for positioning the dropdown in spinnerMode="dropdown". [dimension] +android:dropDownSelector : List selector to use for spinnerMode="dropdown" display. [reference, color] +android:dropDownVerticalOffset : Vertical offset from the spinner widget for positioning the dropdown in spinnerMode="dropdown". [dimension] +android:dropDownWidth : Width of the dropdown in spinnerMode="dropdown". [dimension, enum] +android:duplicateParentState : When this attribute is set to true, the view gets its drawable state (focused, pressed, etc.) from its direct parent rather than from itself. [boolean] +android:editable : If set, specifies that this TextView has an input method. * Deprecated: Use inputType instead. [boolean] +android:editorExtras : Reference to an "input-extras" XML resource containing additional data to supply to an input method, which is private to the implementation of the input method. [reference] +android:ellipsize : If set, causes words that are longer than the view is wide to be ellipsized instead of broken in the middle. [enum] +android:ems : Makes the TextView be exactly this many ems wide. [integer] +android:enabled : Specifies whether the TextView is enabled or not. * Deprecated: Use state_enabled instead. [boolean] +android:endYear : The last year (inclusive), for example "2010". [integer] +android:entries : Reference to an array resource that will populate the Spinner. [reference] +android:eventsInterceptionEnabled : Defines whether the overlay should intercept the motion events when a gesture is recognized. [boolean] +android:fadeDuration : Duration, in milliseconds, of the fade out effect after the user is done drawing a gesture. [integer] +android:fadeEnabled : Defines whether the gesture will automatically fade out after being recognized. [boolean] +android:fadeOffset : Time, in milliseconds, to wait before the gesture fades out after the user is done drawing it. [integer] +android:fadeScrollbars : Defines whether to fade out scrollbars when they are not in use. [boolean] +android:fadingEdge : Defines which edges should be fadeded on scrolling. [flag] +android:fadingEdgeLength : Defines the length of the fading edges. [dimension] +android:fastScrollAlwaysVisible : When set to true, the list will always show the fast scroll interface. [boolean] +android:fastScrollEnabled : Enables the fast scroll thumb that can be dragged to quickly scroll through the list. [boolean] +android:fillViewport : Defines whether the scrollview should stretch its content to fill the viewport. [boolean] +android:filterTouchesWhenObscured : Specifies whether to filter touches when the view's window is obscured by another visible window. [boolean] +android:firstDayOfWeek : The first day of week according to java.util.Calendar. [integer] +android:fitsSystemWindows : Boolean internal attribute to adjust view layout based on system windows such as the status bar. [boolean] +android:flingable : @hide Whether the number picker supports fligning. [boolean] +android:flipInterval : [integer] +android:focusable : Boolean that controls whether a view can take focus. [boolean] +android:focusableInTouchMode : Boolean that controls whether a view can take focus while in touch mode. [boolean] +android:focusedMonthDateColor : The color for the dates of the selected month. [reference, color] +android:footerDividersEnabled : When set to false, the ListView will not draw the divider before each footer view. [boolean] +android:foreground : Defines the drawable to draw over the content. [reference, color] +android:foregroundGravity : Defines the gravity to apply to the foreground drawable. [flag] +android:foregroundInsidePadding : Defines whether the foreground drawable should be drawn inside the padding. [boolean] +android:format : Format string: if specified, the Chronometer will display this string, with the first "%s" replaced by the current timer value in "MM:SS" or "H:MM:SS" form. [string] +android:freezesText : If set, the text view will include its current complete text inside of its frozen icicle in addition to meta-data such as the current cursor position. [boolean] +android:gestureColor : Color used to draw a gesture. [color] +android:gestureStrokeAngleThreshold : Minimum curve angle a stroke must contain before it is recognized as a gesture. [float] +android:gestureStrokeLengthThreshold : Minimum length of a stroke before it is recognized as a gesture. [float] +android:gestureStrokeSquarenessThreshold : Squareness threshold of a stroke before it is recognized as a gesture. [float] +android:gestureStrokeType : Defines the type of strokes that define a gesture. [enum] +android:gestureStrokeWidth : Width of the stroke used to draw the gesture. [float] +android:gravity : Specifies how to place the content of an object, both on the x- and y-axis, within the object itself. [flag] +android:groupIndicator : Indicator shown beside the group View. [reference] +android:hand_hour : [reference] +android:hand_minute : [reference] +android:handle : Identifier for the child that represents the drawer's handle. [reference] +android:hapticFeedbackEnabled : Boolean that controls whether a view should have haptic feedback enabled for events such as long presses. [boolean] +android:headerDividersEnabled : When set to false, the ListView will not draw the divider after each header view. [boolean] +android:height : Makes the TextView be exactly this many pixels tall. [dimension] +android:hint : Hint text to display when the text is empty. [string] +android:horizontalSpacing : Defines the default horizontal spacing between columns. [dimension] +android:iconifiedByDefault : The default state of the SearchView. [boolean] +android:id : [reference]. * Required. +android:ignoreGravity : Indicates what view should not be affected by gravity. [reference] +android:imeActionId : Supply a value for EditorInfo.actionId used when an input method is connected to the text view. [integer] +android:imeActionLabel : Supply a value for EditorInfo.actionLabel used when an input method is connected to the text view. [string] +android:imeOptions : Additional features you can enable in an IME associated with an editor to improve the integration with your application. [flag] +android:inAnimation : Identifier for the animation to use when a view is shown. [reference] +android:includeFontPadding : Leave enough room for ascenders and descenders instead of using the font ascent and descent strictly. [boolean] +android:indeterminate : Allows to enable the indeterminate mode. [boolean] +android:indeterminateBehavior : Defines how the indeterminate mode should behave when the progress reaches max. [enum] +android:indeterminateDrawable : Drawable used for the indeterminate mode. [reference] +android:indeterminateDuration : Duration of the indeterminate animation. [integer] +android:indeterminateOnly : Restricts to ONLY indeterminate mode (state-keeping progress mode will not work). [boolean] +android:indicatorLeft : The left bound for an item's indicator. [dimension] +android:indicatorRight : The right bound for an item's indicator. [dimension] +android:inflatedId : Overrides the id of the inflated View with this value. [reference] +android:inputMethod : If set, specifies that this TextView should use the specified input method (specified by fully-qualified class name). * Deprecated: Use inputType instead. [string] +android:inputType : The type of data being placed in a text field, used to help an input method decide how to let the user enter text. [flag] +android:interpolator : [reference] +android:isIndicator : Whether this rating bar is an indicator (and non-changeable by the user). [boolean] +android:isScrollContainer : Set this if the view will serve as a scrolling container, meaing that it can be resized to shrink its overall window so that there will be space for an input method. [boolean] +android:keepScreenOn : Controls whether the view's window should keep the screen on while visible. [boolean] +android:layerType : Specifies the type of layer backing this view. [enum] +layout : [reference]. * Required. +android:layoutAnimation : Defines the layout animation to use the first time the ViewGroup is laid out. [reference] +android:lineSpacingExtra : Extra spacing between lines of text. [dimension] +android:lineSpacingMultiplier : Extra spacing between lines of text, as a multiplier. [float] +android:lines : Makes the TextView be exactly this many lines tall. [integer] +android:linksClickable : If set to false, keeps the movement method from being set to the link movement method even if autoLink causes links to be found. [boolean] +android:listSelector : Drawable used to indicate the currently selected item in the list. [reference, color] +android:longClickable : Defines whether this view reacts to long click events. [boolean] +android:loopViews : Defines whether the animator loops to the first view once it has reached the end of the list. [boolean] +android:marqueeRepeatLimit : The number of times to repeat the marquee animation. [integer, enum] +android:max : Defines the maximum value the progress can take. [integer] +android:maxDate : The minimal date shown by this calendar view in mm/dd/yyyy format. [string] +android:maxEms : Makes the TextView be at most this many ems wide. [integer] +android:maxHeight : An optional argument to supply a maximum height for this view. [dimension] +android:maxLength : Set an input filter to constrain the text length to the specified number. [integer] +android:maxLines : Makes the TextView be at most this many lines tall. [integer] +android:maxWidth : An optional argument to supply a maximum width for this view. [dimension] +android:measureAllChildren : Determines whether to measure all children or just those in the VISIBLE or INVISIBLE state when measuring. [boolean] +android:measureWithLargestChild : When set to true, all children with a weight will be considered having the minimum size of the largest child. [boolean] +android:minDate : The minimal date shown by this calendar view in mm/dd/yyyy format. [string] +android:minEms : Makes the TextView be at least this many ems wide. [integer] +android:minHeight : Defines the minimum height of the view. +android:minLines : Makes the TextView be at least this many lines tall. [integer] +android:minWidth : Defines the minimum width of the view. +android:mode : [enum] +android:nextFocusDown : Defines the next view to give focus to when the next focus is FOCUS_DOWN If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] +android:nextFocusForward : Defines the next view to give focus to when the next focus is FOCUS_FORWARD If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] +android:nextFocusLeft : Defines the next view to give focus to when the next focus is FOCUS_LEFT. [reference] +android:nextFocusRight : Defines the next view to give focus to when the next focus is FOCUS_RIGHT If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] +android:nextFocusUp : Defines the next view to give focus to when the next focus is FOCUS_UP If the reference refers to a view that does not exist or is part of a hierarchy that is invisible, a java.lang.RuntimeException will result when the reference is accessed. [reference] +android:numColumns : Defines how many columns to show. [integer, enum] +android:numStars : The number of stars (or rating items) to show. [integer] +android:numeric : If set, specifies that this TextView has a numeric input method. * Deprecated: Use inputType instead. [flag] +android:onClick : Name of the method in this View's context to invoke when the view is clicked. [string] +android:orientation : Should the layout be a column or a row? Use "horizontal" for a row, "vertical" for a column. [enum] +android:outAnimation : Identifier for the animation to use when a view is hidden. [reference] +android:overScrollFooter : Drawable to draw below list content. [reference, color] +android:overScrollHeader : Drawable to draw above list content. [reference, color] +android:overScrollMode : Defines over-scrolling behavior. [enum] +android:padding : Sets the padding, in pixels, of all four edges. [dimension] +android:paddingBottom : Sets the padding, in pixels, of the bottom edge; see padding. [dimension] +android:paddingLeft : Sets the padding, in pixels, of the left edge; see padding. [dimension] +android:paddingRight : Sets the padding, in pixels, of the right edge; see padding. [dimension] +android:paddingTop : Sets the padding, in pixels, of the top edge; see padding. [dimension] +android:password : Whether the characters of the field are displayed as password dots instead of themselves. * Deprecated: Use inputType instead. [boolean] +android:persistentDrawingCache : Defines the persistence of the drawing cache. [flag] +android:phoneNumber : If set, specifies that this TextView has a phone number input method. * Deprecated: Use inputType instead. [boolean] +android:popupBackground : Background drawable to use for the dropdown in spinnerMode="dropdown". [reference, color] +android:popupPromptView : Reference to a layout to use for displaying a prompt in the dropdown for spinnerMode="dropdown". [reference] +android:privateImeOptions : An addition content type description to supply to the input method attached to the text view, which is private to the implementation of the input method. [string] +android:progress : Defines the default progress value, between 0 and max. [integer] +android:progressDrawable : Drawable used for the progress mode. [reference] +android:prompt : The prompt to display when the spinner's dialog is shown. [reference] +android:queryHint : An optional query hint string to be displayed in the empty query field. [string] +android:quickContactWindowSize : [enum] +android:rating : The rating to set by default. [float] +android:rotation : rotation of the view, in degrees. [float] +android:rotationX : rotation of the view around the x axis, in degrees. [float] +android:rotationY : rotation of the view around the y axis, in degrees. [float] +android:saveEnabled : If unset, no state will be saved for this view when it is being frozen. [boolean] +android:scaleType : Controls how the image should be resized or moved to match the size of this ImageView. [enum] +android:scaleX : scale of the view in the x direction. [float] +android:scaleY : scale of the view in the y direction. [float] +android:scrollHorizontally : Whether the text is allowed to be wider than the view (and therefore can be scrolled horizontally). [boolean] +android:scrollX : The initial horizontal scroll offset, in pixels. [dimension] +android:scrollY : The initial vertical scroll offset, in pixels. [dimension] +android:scrollbarAlwaysDrawHorizontalTrack : Defines whether the horizontal scrollbar track should always be drawn. [boolean] +android:scrollbarAlwaysDrawVerticalTrack : Defines whether the vertical scrollbar track should always be drawn. [boolean] +android:scrollbarDefaultDelayBeforeFade : Defines the delay in milliseconds that a scrollbar waits before fade out. [integer] +android:scrollbarFadeDuration : Defines the delay in milliseconds that a scrollbar takes to fade out. [integer] +android:scrollbarSize : Sets the width of vertical scrollbars and height of horizontal scrollbars. [dimension] +android:scrollbarStyle : Controls the scrollbar style and position. [enum] +android:scrollbarThumbHorizontal : Defines the horizontal scrollbar thumb drawable. [reference] +android:scrollbarThumbVertical : Defines the vertical scrollbar thumb drawable. [reference] +android:scrollbarTrackHorizontal : Defines the horizontal scrollbar track drawable. [reference] +android:scrollbarTrackVertical : Defines the vertical scrollbar track drawable. [reference] +android:scrollbars : Defines which scrollbars should be displayed on scrolling or not. [flag] +android:scrollingCache : When set to true, the list uses a drawing cache during scrolling. [boolean] +android:secondaryProgress : Defines the secondary progress value, between 0 and max. [integer] +android:selectAllOnFocus : If the text is selectable, select it all when the view takes focus instead of moving the cursor to the start or end. [boolean] +android:selectedDateVerticalBar : Drawable for the vertical bar shown at the beggining and at the end of a selected date. [reference] +android:selectedWeekBackgroundColor : The background color for the selected week. [reference, color] +android:selectionDivider : @hide The divider for making the selection area. [reference] +android:selectionDividerHeight : @hide The height of the selection divider. [dimension] +android:shadowColor : Place a shadow of the specified color behind the text. [color] +android:shadowDx : Horizontal offset of the shadow. [float] +android:shadowDy : Vertical offset of the shadow. [float] +android:shadowRadius : Radius of the shadow. [float] +android:showDividers : Setting for which dividers to show. [flag] +android:showWeekNumber : Whether do show week numbers. [boolean] +android:shownWeekCount : The number of weeks to be shown. [integer] +android:shrinkColumns : The zero-based index of the columns to shrink. [string] +android:singleLine : Constrains the text to a single horizontally scrolling line instead of letting it wrap onto multiple lines, and advances focus instead of inserting a newline when you press the enter key. * Deprecated: This attribute is deprecated and is replaced by the textMultiLine flag in the inputType attribute. Use caution when altering existing layouts, as the default value of singeLine is false (multi-line mode), but if you specify any value for inputType, the default is single-line mode. (If both singleLine and inputType attributes are found, the inputType flags will override the value of singleLine.). [boolean] +android:smoothScrollbar : When set to true, the list will use a more refined calculation method based on the pixels height of the items visible on screen. [boolean] +android:solidColor : @hide Color for the solid color background if such for optimized rendering. [reference, color] +android:soundEffectsEnabled : Boolean that controls whether a view should have sound effects enabled for events such as clicking and touching. [boolean] +android:spacing : [dimension] +android:spinnerMode : Display mode for spinner options. [enum] +android:spinnersShown : Whether the spinners are shown. [boolean] +android:splitMotionEvents : Sets whether this ViewGroup should split MotionEvents to separate child views during touch event dispatch. [boolean] +android:src : Sets a drawable as the content of this ImageView. [reference, color] +android:stackFromBottom : Used by ListView and GridView to stack their content from the bottom. [boolean] +android:startYear : The first year (inclusive), for example "1940". [integer] +android:stepSize : The step size of the rating. [float] +android:stretchColumns : The zero-based index of the columns to stretch. [string] +android:stretchMode : Defines how columns should stretch to fill the available empty space, if any. [enum] +style : A reference to a custom style [reference] +android:tabLayout : Layout used to organize each tab's content. [reference] +android:tabStripEnabled : Determines whether the strip under the tab indicators is drawn or not. [boolean] +android:tabStripLeft : Drawable used to draw the left part of the strip underneath the tabs. [reference] +android:tabStripRight : Drawable used to draw the right part of the strip underneath the tabs. [reference] +android:tag : Supply a tag for this view containing a String, to be retrieved later with View.getTag() or searched for with View.findViewWithTag() . [string] +android:text : Text to display. [string] +android:textAppearance : Base text color, typeface, size, and style. [reference] +android:textColor : Text color. [reference, color] +android:textColorHighlight : Color of the text selection highlight. [reference, color] +android:textColorHint : Color of the hint text. [reference, color] +android:textColorLink : Text color for links. [reference, color] +android:textCursorDrawable : Reference to a drawable that will be drawn under the insertion cursor. [reference] +android:textEditNoPasteWindowLayout : Variation of textEditPasteWindowLayout displayed when the clipboard is empty. [reference] +android:textEditPasteWindowLayout : The layout of the view that is displayed on top of the cursor to paste inside a TextEdit field. [reference] +android:textEditSideNoPasteWindowLayout : Variation of textEditSidePasteWindowLayout displayed when the clipboard is empty. [reference] +android:textEditSidePasteWindowLayout : Used instead of textEditPasteWindowLayout when the window is moved on the side of the insertion cursor because it would be clipped if it were positioned on top. [reference] +android:textFilterEnabled : When set to true, the list will filter results as the user types. [boolean] +android:textIsSelectable : Indicates that the content of a non-editable text can be selected. [boolean] +android:textOff : The text for the button when it is not checked. [string] +android:textOn : The text for the button when it is checked. [string] +android:textScaleX : Sets the horizontal scaling factor for the text. [float] +android:textSelectHandle : Reference to a drawable that will be used to display a text selection anchor for positioning the cursor within text. [reference] +android:textSelectHandleLeft : Reference to a drawable that will be used to display a text selection anchor on the left side of a selection region. [reference] +android:textSelectHandleRight : Reference to a drawable that will be used to display a text selection anchor on the right side of a selection region. [reference] +android:textSize : Size of the text. [dimension] +android:textStyle : Style (bold, italic, bolditalic) for the text. [flag] +android:thumb : Draws the thumb on a seekbar. [reference] +android:thumbOffset : An offset for the thumb that allows it to extend out of the range of the track. [dimension] +android:tint : Set a tinting color for the image. [color] +android:topOffset : Extra offset for the handle at the top of the SlidingDrawer. [dimension] +android:transcriptMode : Sets the transcript mode for the list. [enum] +android:transformPivotX : x location of the pivot point around which the view will rotate and scale. [dimension] +android:transformPivotY : y location of the pivot point around which the view will rotate and scale. [dimension] +android:translationX : translation in x of the view. [dimension] +android:translationY : translation in y of the view. [dimension] +android:typeface : Typeface (normal, sans, serif, monospace) for the text. [enum] +android:uncertainGestureColor : Color used to draw the user's strokes until we are sure it's a gesture. [color] +android:unfocusedMonthDateColor : The color for the dates of an unfocused month. [reference, color] +android:unselectedAlpha : Sets the alpha on the items that are not selected. [float] +android:verticalScrollbarPosition : Determines which side the vertical scroll bar should be placed on. [enum] +android:verticalSpacing : Defines the default vertical spacing between rows. [dimension] +android:visibility : Controls the initial visibility of the view. [enum] +android:weekDayTextAppearance : The text appearance for the week day abbreviation of the calendar header. [reference] +android:weekNumberColor : The color for the week numbers. [reference, color] +android:weekSeparatorLineColor : The color for the sepatator line between weeks. [reference, color] +android:weightSum : Defines the maximum weight sum. [float] +android:width : Makes the TextView be exactly this many pixels wide. [dimension] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion33.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion33.txt new file mode 100644 index 0000000..f9c008d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion33.txt @@ -0,0 +1,3 @@ +Code completion in completionvalues1.xml for <item name="android:allowSingleTap">^</item>: +true +false diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion34.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion34.txt new file mode 100644 index 0000000..861a413 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion34.txt @@ -0,0 +1,3 @@ +Code completion in completionvalues1.xml for <item name="android:alwaysDrawnWithCache">^ false </item>: +true +false diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion35.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion35.txt new file mode 100644 index 0000000..25e8a0b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion35.txt @@ -0,0 +1,3 @@ +Code completion in completionvalues1.xml for <item name="android:alwaysDrawnWithCache"> ^false </item>: +true +false diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion36.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion36.txt new file mode 100644 index 0000000..d85133f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion36.txt @@ -0,0 +1,2 @@ +Code completion in completionvalues1.xml for <item name="android:alwaysDrawnWithCache"> f^alse </item>: +false diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion37.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion37.txt new file mode 100644 index 0000000..1d41351 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion37.txt @@ -0,0 +1,2 @@ +Code completion in completionvalues1.xml for <item name="android:orientation">h^</item>: +horizontal diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion38.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion38.txt new file mode 100644 index 0000000..8819bcd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion38.txt @@ -0,0 +1,6 @@ +Code completion in completionvalues1.xml for c^: +center_vertical +center_horizontal +center +clip_vertical +clip_horizontal diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1.xml new file mode 100644 index 0000000..89b5f46 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <style name="stylename"> + <item name="android:textSize">17sp</item> + <item name="android:textColor">@color/title_color</item> + <item name="android:shadowColor">@an</item> + <item name="android:gravity"> </item> + <item name="gr">@color/title_color</item> + <item name="an">@color/title_color</item> + <item ></item> + <item name=""></item> + <item name="android:allowSingleTap"></item> + <item name="android:alwaysDrawnWithCache"> false </item> + <item name="android:orientation">h</item> + <item name="android:gravity"> + c + </item> + </style> +</resources> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt new file mode 100644 index 0000000..edf4892 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt @@ -0,0 +1,13 @@ +Code completion in drawable1.xml for ^<layer-list: +<animated-rotate /> +<animation-list /> : Drawable used to render several animated frames. +<bitmap /> : Drawable used to draw bitmaps. +<clip /> +<color /> : Drawable used to draw a single color. +<inset /> +<layer-list ></layer-list> : Drawable used to render several drawables stacked on top of each other. +<nine-patch /> : Drawable used to draw 9-patches. +<rotate /> : Drawable used to rotate another drawable. +<scale /> +<selector ></selector> : Drawable used to render several states. +<shape ></shape> : Drawable used to render a geometric shape, with a gradient or a solid color. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion48.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion48.txt new file mode 100644 index 0000000..f98bb4c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion48.txt @@ -0,0 +1,3 @@ +Code completion in drawable1.xml for ^xmlns:android: +android:opacity : Indicates the opacity of the layer. [enum] +xmlns:android diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion49.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion49.txt new file mode 100644 index 0000000..4d3dfe3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion49.txt @@ -0,0 +1,7 @@ +Code completion in drawable1.xml for <item ^></item>: +android:left : Left coordinate of the layer. [dimension] +android:top : Top coordinate of the layer. [dimension] +android:right : Right coordinate of the layer. [dimension] +android:bottom : Bottom coordinate of the layer. [dimension] +android:drawable : Drawable used to render the layer. [reference] +android:id : Identifier of the layer. [reference] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt new file mode 100644 index 0000000..90eab10 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt @@ -0,0 +1,13 @@ +Code completion in drawable1.xml for <item >^</item>: +<animated-rotate /> +<animation-list /> : Drawable used to render several animated frames. +<bitmap /> : Drawable used to draw bitmaps. +<clip /> +<color /> : Drawable used to draw a single color. +<inset /> +<layer-list ></layer-list> : Drawable used to render several drawables stacked on top of each other. +<nine-patch /> : Drawable used to draw 9-patches. +<rotate /> : Drawable used to rotate another drawable. +<scale /> +<selector ></selector> : Drawable used to render several states. +<shape ></shape> : Drawable used to render a geometric shape, with a gradient or a solid color. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1.xml new file mode 100644 index 0000000..9513f17 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list + xmlns:android="http://schemas.android.com/apk/res/android"> + <item ></item> +</layer-list> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2-expected-completion51.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2-expected-completion51.txt new file mode 100644 index 0000000..1471845 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2-expected-completion51.txt @@ -0,0 +1,10 @@ +Code completion in drawable2.xml for ^android:innerRadiusRatio="2": +android:visible : Indicates whether the drawable should intially be visible. [boolean] +android:dither : Enables or disables dithering. [boolean] +android:shape : Indicates what shape to fill with a gradient. [enum] +android:innerRadiusRatio : Inner radius of the ring expressed as a ratio of the ring's width. [float] +android:thicknessRatio : Thickness of the ring expressed as a ratio of the ring's width. [float] +android:innerRadius : Inner radius of the ring. [dimension] +android:thickness : Thickness of the ring. [dimension] +android:useLevel : Indicates whether the drawable's level affects the way the gradient is drawn. +xmlns:android diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2-expected-completion52.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2-expected-completion52.txt new file mode 100644 index 0000000..2a28533 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2-expected-completion52.txt @@ -0,0 +1,7 @@ +Code completion in drawable2.xml for ^<gradient: +<corners /> : Describes the corners for the rectangle shape of a GradientDrawable. +<gradient /> : Used to describe the gradient used to fill the shape of a GradientDrawable. +<padding /> : Used to specify the optional padding of a GradientDrawable. +<size /> : Used to specify the size of the shape for GradientDrawable. +<solid /> : Used to fill the shape of GradientDrawable with a solid color. +<stroke /> : Used to describe the optional stroke of a GradientDrawable. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2.xml new file mode 100644 index 0000000..c6a672f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable2.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:innerRadiusRatio="2"> + <gradient /> +</shape> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff new file mode 100644 index 0000000..d734ccf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1b.diff @@ -0,0 +1,3 @@ +< <Button android:text="Button" +--- +> <Button style="@style/newstyle" android:text="Button" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff new file mode 100644 index 0000000..2ebc91d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1c.diff @@ -0,0 +1,4 @@ + android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" + </FrameLayout> +--- diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff new file mode 100644 index 0000000..ec560b3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract1d.diff @@ -0,0 +1,6 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff new file mode 100644 index 0000000..ec560b3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract2.diff @@ -0,0 +1,6 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff new file mode 100644 index 0000000..f7fd22b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract3.diff @@ -0,0 +1,15 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="wrap_content" +< android:textColor="#FF0000" android:textSize="20pt" +< android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +< android:id="@+id/button2" android:layout_alignParentBottom="true"></Button> +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="wrap_content" +> android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" +> android:textColor="#FF00FF" android:id="@+id/button2" android:layout_alignParentBottom="true"></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff new file mode 100644 index 0000000..a8e2af4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract4.diff @@ -0,0 +1,7 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="fill_parent" +> android:textSize="20pt" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff new file mode 100644 index 0000000..bcaff2a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract5.diff @@ -0,0 +1,8 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="wrap_content" +< android:textColor="#FF0000" android:textSize="20pt" +< android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +--- +> <Button style="@style/newstyle" android:text="Button" +> android:layout_width="wrap_content" android:layout_height="wrap_content" +> android:textColor="#FF0000" android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff new file mode 100644 index 0000000..1db5e38 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract6.diff @@ -0,0 +1,6 @@ +< <Button android:text="Button" +< android:layout_width="wrap_content" android:layout_height="wrap_content" +< android:textColor="#FF0000" android:textSize="20pt" +< android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> +--- +> <Button style="@style/newstyle" android:id="@+id/button1" ></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff new file mode 100644 index 0000000..2ebc91d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1-expected-extract8.diff @@ -0,0 +1,4 @@ + android:layout_width="wrap_content" android:layout_height="fill_parent" +< android:textColor="#FF00FF" android:textSize="20pt" + </FrameLayout> +--- diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info new file mode 100644 index 0000000..69f7739 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.info @@ -0,0 +1,3 @@ +android.widget.LinearLayout [0,36,140,320] <LinearLayout> + android.widget.Button [0,0,140,62] <Button> + android.widget.Button [0,62,140,284] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml new file mode 100644 index 0000000..64c49b2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle1.xml @@ -0,0 +1,11 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" android:layout_height="match_parent"> + <Button android:text="Button" + android:layout_width="wrap_content" android:layout_height="wrap_content" + android:textColor="#FF0000" android:textSize="20pt" + android:id="@+id/button1" android:layout_alignParentBottom="true"></Button> + <Button android:text="Button" + android:layout_width="wrap_content" android:layout_height="fill_parent" + android:textColor="#FF00FF" android:textSize="20pt" + android:id="@+id/button2" android:layout_alignParentBottom="true"></Button> +</FrameLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff new file mode 100644 index 0000000..84c2ad7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2-expected-extract7.diff @@ -0,0 +1,13 @@ +< <Button foo:text="Button" +< foo:layout_width="wrap_content" foo:layout_height="wrap_content" +< foo:textColor="#FF0000" foo:textSize="20pt" +< foo:id="@+id/button1" foo:layout_alignParentBottom="true"></Button> +< <Button foo:text="Button" +< foo:layout_width="wrap_content" foo:layout_height="fill_parent" +< foo:textColor="#00FF00" foo:textSize="20pt" +--- +> <Button style="@style/newstyle" foo:text="Button" +> foo:layout_width="wrap_content" foo:layout_height="wrap_content" +> foo:textColor="#FF0000" foo:id="@+id/button1" foo:layout_alignParentBottom="true"></Button> +> <Button style="@style/newstyle" foo:text="Button" +> foo:layout_width="wrap_content" foo:layout_height="fill_parent" diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info new file mode 100644 index 0000000..69f7739 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.info @@ -0,0 +1,3 @@ +android.widget.LinearLayout [0,36,140,320] <LinearLayout> + android.widget.Button [0,0,140,62] <Button> + android.widget.Button [0,62,140,284] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml new file mode 100644 index 0000000..3cb966f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/extractstyle2.xml @@ -0,0 +1,11 @@ +<LinearLayout xmlns:foo="http://schemas.android.com/apk/res/android" + foo:layout_width="wrap_content" foo:layout_height="match_parent" foo:orientation="vertical"> + <Button foo:text="Button" + foo:layout_width="wrap_content" foo:layout_height="wrap_content" + foo:textColor="#FF0000" foo:textSize="20pt" + foo:id="@+id/button1" foo:layout_alignParentBottom="true"></Button> + <Button foo:text="Button" + foo:layout_width="wrap_content" foo:layout_height="fill_parent" + foo:textColor="#00FF00" foo:textSize="20pt" + foo:id="@+id/button2" foo:layout_alignParentBottom="true"></Button> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion14.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion14.txt new file mode 100644 index 0000000..478e435 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion14.txt @@ -0,0 +1,10 @@ +Code completion in manifest.xml for android.permission.ACC^ESS_NETWORK_STATE: +android.permission.ACCESS_CHECKIN_PROPERTIES +android.permission.ACCESS_COARSE_LOCATION +android.permission.ACCESS_FINE_LOCATION +android.permission.ACCESS_LOCATION_EXTRA_COMMANDS +android.permission.ACCESS_MOCK_LOCATION +android.permission.ACCESS_NETWORK_STATE +android.permission.ACCESS_SURFACE_FLINGER +android.permission.ACCESS_WIFI_STATE +android.permission.ACCOUNT_MANAGER diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion15.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion15.txt new file mode 100644 index 0000000..c6b3538 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion15.txt @@ -0,0 +1,3 @@ +Code completion in manifest.xml for android.intent.category.L^AUNCHER: +android.intent.category.LAUNCHER +android.intent.category.LE_DESK_DOCK diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion16.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion16.txt new file mode 100644 index 0000000..4795c5c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion16.txt @@ -0,0 +1,14 @@ +Code completion in manifest.xml for <^application android:i: +application : The "application" tag describes application-level components contained in the package, as well as general application attributes. +compatible-screens +instrumentation : Attributes that can be supplied in an AndroidManifest.xml "instrumentation" tag, a child of the root manifest tag. +original-package : Private tag to declare the original package name that this package is based on. +permission : The "permission" tag declares a security permission that can be used to control access from other packages to specific components or features in your package (or other packages). +permission-group : The "permission-group" tag declares a logical grouping of related permissions. +permission-tree : The "permission-tree" tag declares the base of a tree of permission values: it declares that this package has ownership of the given permission name, as well as all names underneath it (separated by '.'). +protected-broadcast : Private tag to declare system protected broadcast actions. +supports-screens : The "supports-screens" specifies the screen dimensions an application supports. +uses-configuration : The "uses-configuration" tag specifies a specific hardware configuration value used by the application. +uses-feature : The "uses-feature" tag specifies a specific feature used by the application. +uses-permission : The "uses-permission" tag requests a "permission" that the containing package must be granted in order for it to operate correctly. +uses-sdk : The "uses-sdk" tag describes the SDK features that the containing package must be running on to operate correctly. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion17.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion17.txt new file mode 100644 index 0000000..6d966da --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion17.txt @@ -0,0 +1,7 @@ +Code completion in manifest.xml for ^android:versionCode="1": +package : This attribute gives a unique name for the package, using a Java-style naming convention to avoid name collisions. For example, applications published by Google could have names of the form com.google.app.appname +android:versionCode : Internal version code. [integer] +android:versionName : The text shown to the user to indicate the version they have. [string] +android:sharedUserId : Specify the name of a user ID that will be shared between multiple packages. [string] +android:sharedUserLabel : Specify a label for the shared user UID of this package. [reference] +android:installLocation : The default install location defined by an application. [enum] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion18.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion18.txt new file mode 100644 index 0000000..6fd1096 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion18.txt @@ -0,0 +1,27 @@ +Code completion in manifest.xml for <activity android:^name=".TestActivity": +android:name : Required name of the class implementing the activity, deriving from android.app.Activity. [string] +android:theme : The overall theme to use for an activity. [reference] +android:label : A user-legible name for the given item. [string, reference] +android:description : Descriptive text for the associated data. [reference] +android:icon : A Drawable resource providing a graphical representation of its associated item. [reference] +android:logo : A Drawable resource providing an extended graphical logo for its associated item. [reference] +android:launchMode : Specify how an activity should be launched. [enum] +android:screenOrientation : Specify the orientation an activity should be run in. [enum] +android:configChanges : Specify one or more configuration changes that the activity will handle itself. [flag] +android:permission : Specify a permission that a client is required to have in order to use the associated object. [string] +android:multiprocess : Specify whether a component is allowed to have multiple instances of itself running in different processes. [boolean] +android:process : Specify a specific process that the associated code is to run in. [string] +android:taskAffinity : Specify a task name that activities have an "affinity" to. [string] +android:allowTaskReparenting : Specify that an activity can be moved out of a task it is in to the task it has an affinity for when appropriate. [boolean] +android:finishOnTaskLaunch : Specify whether an activity should be finished when its task is brought to the foreground by relaunching from the home screen. [boolean] +android:finishOnCloseSystemDialogs : Specify whether an activity should be finished when a "close system windows" request has been made. [boolean] +android:clearTaskOnLaunch : Specify whether an activity's task should be cleared when it is re-launched from the home screen. [boolean] +android:noHistory : Specify whether an activity should be kept in its history stack. [boolean] +android:alwaysRetainTaskState : Specify whether an acitivty's task state should always be maintained by the system, or if it is allowed to reset the task to its initial state in certain situations. [boolean] +android:stateNotNeeded : Indicates that an Activity does not need to have its freeze state (as returned by onSaveInstanceState retained in order to be restarted. [boolean] +android:excludeFromRecents : Indicates that an Activity should be excluded from the list of recently launched activities. [boolean] +android:enabled : Specify whether the activity is enabled or not (that is, can be instantiated by the system). [boolean] +android:exported : Flag indicating whether the given application component is available to other applications. [boolean] +android:windowSoftInputMode : Specify the default soft-input mode for the main window of this activity. [flag] +android:immersive : Flag declaring this activity to be 'immersive'; immersive activities should not be interrupted with other activities or notifications. [boolean] +android:hardwareAccelerated : <p>Flag indicating whether the application's rendering should be hardware accelerated if possible. [boolean] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate10.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate10.txt new file mode 100644 index 0000000..c745f54 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate10.txt @@ -0,0 +1,6 @@ +Go To Declaration in manifest.xml for <uses-permission android:name="android.permission.AC^CESS_NETWORK_STATE" />: +Open XML Declaration : [android.permission.ACCESS_NETWORK_STATE] + + +After open, a browser is shown with this URL: + reference/android/Manifest.permission.html#ACCESS_NETWORK_STATE diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate11a.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate11a.txt new file mode 100644 index 0000000..eaeddf9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate11a.txt @@ -0,0 +1,6 @@ +Go To Declaration in manifest.xml for <action android:name="android.intent.ac^tion.MAIN" />: +Open XML Declaration : [android.intent.action.MAIN] + + +After open, a browser is shown with this URL: + reference/android/content/Intent.html#ACTION_MAIN diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate11g.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate11g.txt new file mode 100644 index 0000000..9acb330 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate11g.txt @@ -0,0 +1,6 @@ +Go To Declaration in manifest.xml for <category android:name="android.intent.category.LA^UNCHER" />: +Open XML Declaration : [android.intent.category.LAUNCHER] + + +After open, a browser is shown with this URL: + reference/android/content/Intent.html#CATEGORY_LAUNCHER diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate9a.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate9a.txt new file mode 100644 index 0000000..2221e37 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-navigate9a.txt @@ -0,0 +1,6 @@ +Go To Declaration in manifest.xml for <activity android:name=".Test^Activity": +Open XML Declaration : [.TestActivity] + + +After open, the selected text is: +^<?xml version="1.0" encoding="utf-8"?> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest.xml new file mode 100644 index 0000000..2c0024f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="foo.bar" + android:versionCode="1" + android:versionName="1.0"> + <uses-sdk android:minSdkVersion="11" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + + <application android:icon="@drawable/icon" android:label="@string/app_name"> + <activity android:name=".TestActivity" + 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/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/metadata.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/metadata.xml new file mode 100644 index 0000000..9262f97 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/metadata.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout android:id="@+id/LinearLayout1" + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + <ListView android:layout_width="match_parent" android:id="@+id/listView1" + android:layout_height="wrap_content"> + </ListView> + <Button android:text="Button" android:id="@+id/button1"/> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate1.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate1.txt new file mode 100644 index 0000000..7308a48 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate1.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigation1.xml for android:text="@string/app^_name": +Open Declaration in values/strings.xml : [@string/app_name] + L/PROJECTNAME/res/values/strings.xml + + +After open, the selected text is: + [^<string name="app_name">PROJECTNAME</string>] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate12.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate12.txt new file mode 100644 index 0000000..23fa07e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate12.txt @@ -0,0 +1,6 @@ +Go To Declaration in navigation1.xml for <my.Cust^omView></my.CustomView>: +Open XML Declaration : [my.CustomView] + + +After open, the selected text is: +^<?xml version="1.0" encoding="utf-8"?> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate2.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate2.txt new file mode 100644 index 0000000..f91e1f9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate2.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigation1.xml for marginLeft="@android:dimen/app_ico^n_size": +Open Declaration in values/dimens.xml : [@android:dimen/app_icon_size] + data/res/values/dimens.xml + + +After open, the selected text is: + <dimen name="app_icon_size">^48dip</dimen> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate3.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate3.txt new file mode 100644 index 0000000..53776a0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate3.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigation1.xml for style="@android:style/Widget.B^utton": +Open Declaration in values/styles.xml : [@android:style/Widget.Button] + data/res/values/styles.xml + + +After open, the selected text is: + <style name="Widget.Button">^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate4.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate4.txt new file mode 100644 index 0000000..ec5ee38 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate4.txt @@ -0,0 +1,89 @@ +Go To Declaration in navigation1.xml for android:text="@android:st^ring/ok": +Open Declaration in values-en-rGB/strings.xml : [@android:string/ok] + data/res/values-en-rGB/strings.xml +Open Declaration in values/strings.xml : [@android:string/ok] + data/res/values/strings.xml +Open Declaration in values-ar/strings.xml : [@android:string/ok] + data/res/values-ar/strings.xml +Open Declaration in values-bg/strings.xml : [@android:string/ok] + data/res/values-bg/strings.xml +Open Declaration in values-ca/strings.xml : [@android:string/ok] + data/res/values-ca/strings.xml +Open Declaration in values-cs/strings.xml : [@android:string/ok] + data/res/values-cs/strings.xml +Open Declaration in values-da/strings.xml : [@android:string/ok] + data/res/values-da/strings.xml +Open Declaration in values-de/strings.xml : [@android:string/ok] + data/res/values-de/strings.xml +Open Declaration in values-el/strings.xml : [@android:string/ok] + data/res/values-el/strings.xml +Open Declaration in values-es/strings.xml : [@android:string/ok] + data/res/values-es/strings.xml +Open Declaration in values-es-rUS/strings.xml : [@android:string/ok] + data/res/values-es-rUS/strings.xml +Open Declaration in values-fa/strings.xml : [@android:string/ok] + data/res/values-fa/strings.xml +Open Declaration in values-fi/strings.xml : [@android:string/ok] + data/res/values-fi/strings.xml +Open Declaration in values-fr/strings.xml : [@android:string/ok] + data/res/values-fr/strings.xml +Open Declaration in values-hr/strings.xml : [@android:string/ok] + data/res/values-hr/strings.xml +Open Declaration in values-hu/strings.xml : [@android:string/ok] + data/res/values-hu/strings.xml +Open Declaration in values-in/strings.xml : [@android:string/ok] + data/res/values-in/strings.xml +Open Declaration in values-it/strings.xml : [@android:string/ok] + data/res/values-it/strings.xml +Open Declaration in values-iw/strings.xml : [@android:string/ok] + data/res/values-iw/strings.xml +Open Declaration in values-ja/strings.xml : [@android:string/ok] + data/res/values-ja/strings.xml +Open Declaration in values-ko/strings.xml : [@android:string/ok] + data/res/values-ko/strings.xml +Open Declaration in values-lt/strings.xml : [@android:string/ok] + data/res/values-lt/strings.xml +Open Declaration in values-lv/strings.xml : [@android:string/ok] + data/res/values-lv/strings.xml +Open Declaration in values-nb/strings.xml : [@android:string/ok] + data/res/values-nb/strings.xml +Open Declaration in values-nl/strings.xml : [@android:string/ok] + data/res/values-nl/strings.xml +Open Declaration in values-pl/strings.xml : [@android:string/ok] + data/res/values-pl/strings.xml +Open Declaration in values-pt/strings.xml : [@android:string/ok] + data/res/values-pt/strings.xml +Open Declaration in values-pt-rPT/strings.xml : [@android:string/ok] + data/res/values-pt-rPT/strings.xml +Open Declaration in values-rm/strings.xml : [@android:string/ok] + data/res/values-rm/strings.xml +Open Declaration in values-ro/strings.xml : [@android:string/ok] + data/res/values-ro/strings.xml +Open Declaration in values-ru/strings.xml : [@android:string/ok] + data/res/values-ru/strings.xml +Open Declaration in values-sk/strings.xml : [@android:string/ok] + data/res/values-sk/strings.xml +Open Declaration in values-sl/strings.xml : [@android:string/ok] + data/res/values-sl/strings.xml +Open Declaration in values-sr/strings.xml : [@android:string/ok] + data/res/values-sr/strings.xml +Open Declaration in values-sv/strings.xml : [@android:string/ok] + data/res/values-sv/strings.xml +Open Declaration in values-th/strings.xml : [@android:string/ok] + data/res/values-th/strings.xml +Open Declaration in values-tl/strings.xml : [@android:string/ok] + data/res/values-tl/strings.xml +Open Declaration in values-tr/strings.xml : [@android:string/ok] + data/res/values-tr/strings.xml +Open Declaration in values-uk/strings.xml : [@android:string/ok] + data/res/values-uk/strings.xml +Open Declaration in values-vi/strings.xml : [@android:string/ok] + data/res/values-vi/strings.xml +Open Declaration in values-zh-rCN/strings.xml : [@android:string/ok] + data/res/values-zh-rCN/strings.xml +Open Declaration in values-zh-rTW/strings.xml : [@android:string/ok] + data/res/values-zh-rTW/strings.xml + + +After open, the selected text is: + <string name="ok" msgid="5970060430562524910">^"OK"</string> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml new file mode 100644 index 0000000..9c175fc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <Button + android:text="@string/app_name" + android:layout_marginLeft="@android:dimen/app_icon_size" + style="@android:style/Widget.Button" + android:id="@+id/button1" + ></Button> + <my.CustomView></my.CustomView> + <EditText + android:text="@android:string/ok" + </EditText> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff new file mode 100644 index 0000000..141180b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-extract2.diff @@ -0,0 +1,6 @@ +--- + </style> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> + </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate5.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate5.txt new file mode 100644 index 0000000..15c91d1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate5.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigationstyles.xml for parent="android:Theme.Li^ght">: +Open Declaration in values/themes.xml : [android:Theme.Light] + data/res/values/themes.xml + + +After open, the selected text is: + <style name="Theme.Light">^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate6.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate6.txt new file mode 100644 index 0000000..5a4f40a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate6.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigationstyles.xml for parent="android:The^me.Light">: +Open Declaration in values/themes.xml : [android:Theme] + data/res/values/themes.xml + + +After open, the selected text is: + <style name="Theme">^ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate7.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate7.txt new file mode 100644 index 0000000..8f7eb46 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate7.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigationstyles.xml for popupBackground">@android:drawable/spinner_dr^opdown_background</item>: +Open Declaration in drawable/spinner_dropdown_background.xml : [@android:drawable/spinner_dropdown_background] + data/res/drawable/spinner_dropdown_background.xml + + +After open, the selected text is: +^<?xml version="1.0" encoding="utf-8"?> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate8.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate8.txt new file mode 100644 index 0000000..b74c676 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles-expected-navigate8.txt @@ -0,0 +1,7 @@ +Go To Declaration in navigationstyles.xml for colorBackground"> @color/cust^om_theme_color </item>: +Open Declaration in values/navigationstyles.xml : [@color/custom_theme_color] + L/PROJECTNAME/res/values/navigationstyles.xml + + +After open, the selected text is: + [^<color name="custom_theme_color">#b0b0ff</color>] diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles.xml new file mode 100644 index 0000000..da4bbf2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigationstyles.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="custom_theme_color">#b0b0ff</color> + <style name="CustomTheme" parent="android:Theme.Light"> + <item name="android:windowBackground">@color/custom_theme_color</item> + <item name="android:colorBackground"> @color/custom_theme_color </item> + </style> + + <style name="BrowserTheme" parent="@android:Theme.Black"> + <item name="android:autoCompleteTextViewStyle">@style/AutoCompleteTextView</item> + <item name="android:windowNoTitle">true</item> + </style> + + <style name="AutoCompleteTextView"> + <item name="android:focusable">true</item> + <item name="android:focusableInTouchMode">true</item> + <item name="android:clickable">true</item> + <item name="android:completionHintView">@android:layout/simple_dropdown_item_1line</item> + <item name="android:textAppearance">?android:attr/textAppearanceLargeInverse</item> + <item name="android:completionThreshold">2</item> + <item name="android:dropDownSelector">@android:drawable/list_selector_background</item> + <item name="android:popupBackground">@android:drawable/spinner_dropdown_background</item> + </style> + + <style name="CustomTitle" parent="@android:Theme"> + <item name="android:windowTitleSize">60dip</item> + </style> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout1-expected-extract1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout1-expected-extract1.xml new file mode 100644 index 0000000..2a0e947 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout1-expected-extract1.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<Button xmlns:android="http://schemas.android.com/apk/res/android" android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout2-expected-extract2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout2-expected-extract2.xml new file mode 100644 index 0000000..b2d1789 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout2-expected-extract2.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +</merge> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract3.xml new file mode 100644 index 0000000..6ba61c8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract3.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:id="@+id/button3" android:layout_height="wrap_content" android:text="Button" ></Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract4.xml new file mode 100644 index 0000000..ee8568e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract4.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" /> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract5.xml new file mode 100644 index 0000000..be57066 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout3-expected-extract5.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> +</merge> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout6-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout6-expected-extract6.diff new file mode 100644 index 0000000..3a4c590 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newlayout6-expected-extract6.diff @@ -0,0 +1,11 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:layout_width="match_parent"> +> <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout5" android:layout_width="wrap_content"> +> <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout6" android:layout_width="wrap_content"> +> <!-- Comment --> +> <Button android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +> </LinearLayout> +> </LinearLayout> +> </LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles-expected-extract1.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles2-expected-extract1b.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract1c.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff new file mode 100644 index 0000000..3b4d930 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles3-expected-extract8.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle" parent="android:Widget.Button"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff new file mode 100644 index 0000000..d83eb49 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract1d.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff new file mode 100644 index 0000000..0685d94 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles4-expected-extract3.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF0000</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff new file mode 100644 index 0000000..f052485 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles5-expected-extract4.diff @@ -0,0 +1,8 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#FF00FF</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff new file mode 100644 index 0000000..ce1d4aa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles6-expected-extract5.diff @@ -0,0 +1,8 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff new file mode 100644 index 0000000..51f0812 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles7-expected-extract6.diff @@ -0,0 +1,13 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:layout_alignParentBottom">true</item> +> <item name="android:layout_height">wrap_content</item> +> <item name="android:layout_width">wrap_content</item> +> <item name="android:text">Button</item> +> <item name="android:textColor">#FF0000</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff new file mode 100644 index 0000000..8f7ad98 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/newstyles8-expected-extract7.diff @@ -0,0 +1,9 @@ +< +--- +> <?xml version="1.0" encoding="utf-8"?> +> <resources xmlns:android="http://schemas.android.com/apk/res/android"> +> <style name="newstyle"> +> <item name="android:textColor">#00FF00</item> +> <item name="android:textSize">20pt</item> +> </style> +> </resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix1.xml new file mode 100644 index 0000000..2ef716b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix1.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="hello">Hello World!</string> + <string name="app_name">PROJECTNAME</string> + <string name="firststring">[^TODO]</string> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix2.xml new file mode 100644 index 0000000..a0d04fb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix2.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="testdimen">[^1dp]</dimen> +</resources> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix3.xml new file mode 100644 index 0000000..8773027 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1-expected-quickFix3.xml @@ -0,0 +1,8 @@ +<?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"> + ^ +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1.xml new file mode 100644 index 0000000..927c8d1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix1.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <Button android:text="@string/firststring" + android:id="@+id/button1" + android:layout_width="@dimen/testdimen" + android:layout_height="wrap_content"> + </Button> + + <include layout="@layout/testlayout" android:layout_width="wrap_content" android:layout_height="wrap_content"/> + +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix2-expected-quickFix4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix2-expected-quickFix4.xml new file mode 100644 index 0000000..025fa0a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix2-expected-quickFix4.xml @@ -0,0 +1,3 @@ +< <color android:color="#0000000"/> +--- +> <color android:color="#0000000" xmlns:android="http://schemas.android.com/apk/res/android"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix2.xml new file mode 100644 index 0000000..4f2a925 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/quickfix2.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + Random comment here + --> +<color android:color="#0000000"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt new file mode 100644 index 0000000..457239f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant1.txt @@ -0,0 +1,3 @@ +Quick assistant in sample1a.xml for <Button android:text="Fir^stButton": +Extract Android String : Initiates the given refactoring operation +Extract Style : Initiates the given refactoring operation diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt new file mode 100644 index 0000000..95187d3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt @@ -0,0 +1,6 @@ +Quick assistant in sample1a.xml for <Bu^tton android:text: +Wrap in Container : Initiates the given refactoring operation +Change Widget Type : Initiates the given refactoring operation +Change Layout : Initiates the given refactoring operation +Extract Style : Initiates the given refactoring operation +Extract as Include : Initiates the given refactoring operation diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt new file mode 100644 index 0000000..8123be4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant3.txt @@ -0,0 +1,2 @@ +Quick assistant in sample1a.xml for <Button andr^oid:text="FirstButton": +Extract Style : Initiates the given refactoring operation diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant4.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant4.txt new file mode 100644 index 0000000..e0cb98b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant4.txt @@ -0,0 +1,2 @@ +Quick assistant in sample1a.xml for android:id="@+id/Linea^rLayout2": +None found. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-changeLayout1a.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-changeLayout1a.xml new file mode 100644 index 0000000..772f82a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-changeLayout1a.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout android:id="@+id/RelativeLayout1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="FirstButton" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button2" android:layout_below="@+id/button1" android:layout_marginTop="2dp" android:text="SecondButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button2"></Button> + <Button android:layout_toRightOf="@+id/button2" android:layout_alignBaseline="@+id/button2" android:layout_alignTop="@+id/button2" android:text="ThirdButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button3"></Button> + <CheckBox android:layout_toRightOf="@+id/button3" android:layout_alignBaseline="@+id/button2" android:layout_below="@+id/button1" android:id="@+id/checkBox1" android:text="CheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content"></CheckBox> + <Button android:layout_alignParentLeft="true" android:layout_below="@+id/checkBox1" android:layout_height="wrap_content" android:text="FourthButton" android:id="@+id/button4" android:layout_width="match_parent"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button5" android:layout_below="@+id/button4" android:layout_gravity="right" android:id="@+id/button5" android:text="FifthButton" android:layout_height="wrap_content" android:layout_width="wrap_content"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button6" android:layout_below="@+id/button5" android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +</RelativeLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-changeView1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-changeView1.xml new file mode 100644 index 0000000..f4a08d0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-changeView1.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout android:id="@+id/LinearLayout2" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <RadioButton android:text="FirstButton" android:id="@+id/RadioButton1" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton> + <LinearLayout android:layout_width="match_parent" android:id="@+id/linearLayout1" android:layout_height="wrap_content"> + <Button android:text="SecondButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button2"></Button> + <Button android:text="ThirdButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button3"></Button> + <CheckBox android:id="@+id/checkBox1" android:text="CheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content"></CheckBox> + </LinearLayout> + <Button android:layout_height="wrap_content" android:text="FourthButton" android:id="@+id/button4" android:layout_width="match_parent"></Button> + <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout3" android:layout_width="match_parent"> + <Button android:layout_gravity="right" android:id="@+id/button5" android:text="FifthButton" android:layout_height="wrap_content" android:layout_width="wrap_content"></Button> + </LinearLayout> + <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:layout_width="match_parent"> + <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout5" android:layout_width="wrap_content"> + <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout6" android:layout_width="wrap_content"> + <RadioButton android:text="Button" android:id="@+id/RadioButton2" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-extract6.diff new file mode 100644 index 0000000..c0ebd59 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-extract6.diff @@ -0,0 +1,9 @@ +< <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:layout_width="match_parent"> +< <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout5" android:layout_width="wrap_content"> +< <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout6" android:layout_width="wrap_content"> +< <Button android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +< </LinearLayout> +< </LinearLayout> +< </LinearLayout> +--- +> <include layout="@layout/newlayout6" android:id="@+id/linearLayout4_ref" android:layout_width="match_parent" android:layout_height="wrap_content"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a.info new file mode 100644 index 0000000..20a23b0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a.info @@ -0,0 +1,13 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.Button [0,0,70,36] <Button> + android.widget.LinearLayout [0,36,240,72] <LinearLayout> + android.widget.Button [0,2,84,38] <Button> + android.widget.Button [84,2,158,38] <Button> + android.widget.CheckBox [158,0,238,36] <CheckBox> + android.widget.Button [0,72,240,108] <Button> + android.widget.LinearLayout [0,108,240,144] <LinearLayout> + android.widget.Button [0,0,71,36] <Button> + android.widget.LinearLayout [0,144,240,180] <LinearLayout> + android.widget.LinearLayout [0,0,49,36] <LinearLayout> + android.widget.LinearLayout [0,0,49,36] <LinearLayout> + android.widget.Button [0,0,49,36] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a.xml new file mode 100644 index 0000000..9a94935 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout android:id="@+id/LinearLayout2" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:text="FirstButton" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <LinearLayout android:layout_width="match_parent" android:id="@+id/linearLayout1" android:layout_height="wrap_content"> + <Button android:text="SecondButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button2"></Button> + <Button android:text="ThirdButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button3"></Button> + <CheckBox android:id="@+id/checkBox1" android:text="CheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content"></CheckBox> + </LinearLayout> + <Button android:layout_height="wrap_content" android:text="FourthButton" android:id="@+id/button4" android:layout_width="match_parent"></Button> + <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout3" android:layout_width="match_parent"> + <Button android:layout_gravity="right" android:id="@+id/button5" android:text="FifthButton" android:layout_height="wrap_content" android:layout_width="wrap_content"></Button> + </LinearLayout> + <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:layout_width="match_parent"> + <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout5" android:layout_width="wrap_content"> + <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout6" android:layout_width="wrap_content"> + <Button android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b-expected-changeLayout1b.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b-expected-changeLayout1b.xml new file mode 100644 index 0000000..f47e9be --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b-expected-changeLayout1b.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + android:id="@+id/RelativeLayout1" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <Button android:layout_alignParentLeft="true" android:layout_alignParentTop="true" + android:text="FirstButton" + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button2" android:layout_below="@+id/button1" android:layout_marginTop="2dp" + android:text="SecondButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button2"></Button> + <Button android:layout_toRightOf="@+id/button2" android:layout_alignBaseline="@+id/button2" android:layout_alignTop="@+id/button2" + android:text="ThirdButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button3"></Button> + <CheckBox android:layout_toRightOf="@+id/button3" android:layout_alignBaseline="@+id/button2" android:layout_below="@+id/button1" + android:id="@+id/checkBox1" + android:text="CheckBox" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></CheckBox> + <Button android:layout_alignParentLeft="true" android:layout_below="@+id/checkBox1" + android:layout_height="wrap_content" + android:text="FourthButton" + android:id="@+id/button4" + android:layout_width="match_parent"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button5" android:layout_below="@+id/button4" + android:layout_gravity="right" + android:id="@+id/button5" + android:text="FifthButton" + android:layout_height="wrap_content" + android:layout_width="wrap_content"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button6" android:layout_below="@+id/button5" + android:text="Button" + android:id="@+id/button6" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></Button> +</RelativeLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b.info new file mode 100644 index 0000000..7807227 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b.info @@ -0,0 +1,14 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.Button [0,0,70,36] <Button> + android.widget.LinearLayout [0,36,240,72] <LinearLayout> + android.widget.Button [0,2,84,38] <Button> + android.widget.Button [84,2,158,38] <Button> + android.widget.CheckBox [158,0,238,36] <CheckBox> + android.widget.Button [0,72,240,108] <Button> + android.widget.LinearLayout [0,108,240,144] <LinearLayout> + android.widget.Button [0,0,71,36] <Button> + android.widget.LinearLayout [0,144,240,180] <LinearLayout> + android.widget.LinearLayout [0,0,49,36] <LinearLayout> + android.widget.LinearLayout [0,0,49,36] <LinearLayout> + android.widget.Button [0,0,49,36] <Button> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b.xml new file mode 100644 index 0000000..6b800ae --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1b.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + android:id="@+id/LinearLayout2" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + <Button + android:text="FirstButton" + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></Button> + <LinearLayout + android:layout_width="match_parent" + android:id="@+id/linearLayout1" + android:layout_height="wrap_content"> + <Button + android:text="SecondButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button2"></Button> + <Button + android:text="ThirdButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/button3"></Button> + <CheckBox + android:id="@+id/checkBox1" + android:text="CheckBox" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></CheckBox> + </LinearLayout> + <Button + android:layout_height="wrap_content" + android:text="FourthButton" + android:id="@+id/button4" + android:layout_width="match_parent"></Button> + <LinearLayout + android:layout_height="wrap_content" + android:id="@+id/linearLayout3" + android:layout_width="match_parent"> + <Button + android:layout_gravity="right" + android:id="@+id/button5" + android:text="FifthButton" + android:layout_height="wrap_content" + android:layout_width="wrap_content"></Button> + </LinearLayout> + <LinearLayout + android:layout_height="wrap_content" + android:id="@+id/linearLayout4" + android:layout_width="match_parent"> + <LinearLayout + android:layout_height="match_parent" + android:id="@+id/linearLayout5" + android:layout_width="wrap_content"> + <LinearLayout + android:layout_height="match_parent" + android:id="@+id/linearLayout6" + android:layout_width="wrap_content"> + <Button + android:text="Button" + android:id="@+id/button6" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></Button> + </LinearLayout> + </LinearLayout> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-changeLayout2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-changeLayout2.xml new file mode 100644 index 0000000..cce3803 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-changeLayout2.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout android:id="@+id/RelativeLayout1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <Button android:layout_below="@+id/button1" android:layout_width="wrap_content" android:layout_alignParentLeft="true" android:id="@+id/button2" android:layout_height="wrap_content" android:text="Button"></Button> + <Button android:layout_width="wrap_content" android:id="@+id/button3" android:layout_below="@+id/button2" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/button2"></Button> + <Button android:layout_width="wrap_content" android:id="@+id/button4" android:layout_below="@+id/button3" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/button3"></Button> + <CheckBox android:layout_width="wrap_content" android:layout_below="@+id/button4" android:id="@+id/checkBox1" android:layout_height="wrap_content" android:text="CheckBox" android:layout_toLeftOf="@+id/button4"></CheckBox> + <Button android:layout_alignTop="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button5" android:layout_alignParentRight="true"></Button> +</RelativeLayout> + + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-changeView2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-changeView2.xml new file mode 100644 index 0000000..5a55498 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-changeView2.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <RelativeLayout android:layout_height="match_parent" android:id="@+id/relativeLayout1" android:layout_width="match_parent"> + <Button android:layout_width="wrap_content" android:layout_alignParentLeft="true" android:id="@+id/button2" android:layout_height="wrap_content" android:text="Button"></Button> + <ImageButton android:layout_width="wrap_content" android:id="@+id/ImageButton1" android:layout_below="@+id/button2" android:layout_height="wrap_content" android:layout_toRightOf="@+id/button2"></ImageButton> + <Button android:layout_width="wrap_content" android:id="@+id/button4" android:layout_below="@+id/ImageButton1" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/ImageButton1"></Button> + <CheckBox android:layout_width="wrap_content" android:layout_below="@+id/button4" android:id="@+id/checkBox1" android:layout_height="wrap_content" android:text="CheckBox" android:layout_toLeftOf="@+id/button4"></CheckBox> + <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/ImageButton2" android:layout_alignParentRight="true"></ImageButton> + </RelativeLayout> +</LinearLayout> + + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-extract3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-extract3.xml new file mode 100644 index 0000000..e4ff731 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2-expected-extract3.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <RelativeLayout android:layout_height="match_parent" android:id="@+id/relativeLayout1" android:layout_width="match_parent"> + <Button android:layout_width="wrap_content" android:layout_alignParentLeft="true" android:id="@+id/button2" android:layout_height="wrap_content" android:text="Button"></Button> + <include layout="@layout/newlayout3" android:id="@+id/button3_ref" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/button2" android:layout_toRightOf="@+id/button2"/> + <Button android:layout_width="wrap_content" android:id="@+id/button4" android:layout_below="@+id/button3_ref" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/button3_ref"></Button> + <CheckBox android:layout_width="wrap_content" android:layout_below="@+id/button4" android:id="@+id/checkBox1" android:layout_height="wrap_content" android:text="CheckBox" android:layout_toLeftOf="@+id/button4"></CheckBox> + <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button5" android:layout_alignParentRight="true"></Button> + </RelativeLayout> +</LinearLayout> + + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2.info new file mode 100644 index 0000000..44d3b62 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2.info @@ -0,0 +1,9 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.Button [0,0,49,36] <Button> + android.widget.RelativeLayout [0,36,240,284] <RelativeLayout> + android.widget.Button [0,0,49,36] <Button> + android.widget.Button [49,36,98,72] <Button> + android.widget.Button [98,72,147,108] <Button> + android.widget.CheckBox [18,108,98,144] <CheckBox> + android.widget.Button [191,0,240,36] <Button> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2.xml new file mode 100644 index 0000000..0697c64 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample2.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <RelativeLayout android:layout_height="match_parent" android:id="@+id/relativeLayout1" android:layout_width="match_parent"> + <Button android:layout_width="wrap_content" android:layout_alignParentLeft="true" android:id="@+id/button2" android:layout_height="wrap_content" android:text="Button"></Button> + <Button android:layout_width="wrap_content" android:id="@+id/button3" android:layout_below="@+id/button2" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/button2"></Button> + <Button android:layout_width="wrap_content" android:id="@+id/button4" android:layout_below="@+id/button3" android:layout_height="wrap_content" android:text="Button" android:layout_toRightOf="@+id/button3"></Button> + <CheckBox android:layout_width="wrap_content" android:layout_below="@+id/button4" android:id="@+id/checkBox1" android:layout_height="wrap_content" android:text="CheckBox" android:layout_toLeftOf="@+id/button4"></CheckBox> + <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button5" android:layout_alignParentRight="true"></Button> + </RelativeLayout> +</LinearLayout> + + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-changeLayout3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-changeLayout3.xml new file mode 100644 index 0000000..c5118b6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-changeLayout3.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:layout_alignParentLeft="true" android:layout_below="@+id/button1" android:layout_above="@+id/button2" android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <Button android:layout_alignParentLeft="true" android:layout_alignParentBottom="true" android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +</RelativeLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract1.xml new file mode 100644 index 0000000..70f576e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract1.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <include layout="@layout/newlayout1" android:id="@+id/button2_ref" android:layout_width="wrap_content" android:layout_height="wrap_content"/> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract2.xml new file mode 100644 index 0000000..9a30a96 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract2.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <include layout="@layout/newlayout2" android:layout_width="wrap_content" android:layout_height="wrap_content"/> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract4.xml new file mode 100644 index 0000000..445f88a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract4.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <include layout="@layout/newlayout3" android:id="@+id/android_logo_ref" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0"/> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract5.xml new file mode 100644 index 0000000..8df41ca --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-extract5.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <include layout="@layout/newlayout3" android:layout_width="wrap_content" android:layout_height="wrap_content"/> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn1.xml new file mode 100644 index 0000000..5af18d6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn1.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn2.xml new file mode 100644 index 0000000..5d5fbfc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn2.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.gesture.GestureOverlayView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + </LinearLayout> +</android.gesture.GestureOverlayView> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn3.xml new file mode 100644 index 0000000..efbeb18 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-expected-wrapIn3.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1-expected-extract4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1-expected-extract4.xml new file mode 100644 index 0000000..d616269 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1-expected-extract4.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <LinearLayout android:id="@+id/newlinear2" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <include layout="@layout/newlayout3" android:id="@+id/android_logo_ref" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1.0"/> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1-expected-extract5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1-expected-extract5.xml new file mode 100644 index 0000000..b37e7be --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1-expected-extract5.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <LinearLayout android:id="@+id/newlinear2" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <include layout="@layout/newlayout3" android:layout_width="wrap_content" android:layout_height="wrap_content"/> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1.xml new file mode 100644 index 0000000..9cf3c43 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation1.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <LinearLayout android:id="@+id/newlinear2" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" + android:focusable="false" android:clickable="false" android:layout_weight="1.0" android:id="@+id/android_logo" /> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2-expected-extract4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2-expected-extract4.xml new file mode 100644 index 0000000..40676ab --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2-expected-extract4.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:customprefix="http://schemas.android.com/apk/res/android" customprefix:id="@+id/newlinear" customprefix:orientation="vertical" customprefix:layout_width="match_parent" customprefix:layout_height="match_parent"> + <include layout="@layout/newlayout3" customprefix:id="@+id/android_logo_ref" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content" customprefix:layout_weight="1.0"/> + <Button customprefix:text="Button" customprefix:id="@+id/button1" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button> + <Button customprefix:text="Button" customprefix:id="@+id/button2" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2-expected-extract5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2-expected-extract5.xml new file mode 100644 index 0000000..c51dc37 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2-expected-extract5.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:customprefix="http://schemas.android.com/apk/res/android" customprefix:id="@+id/newlinear" customprefix:orientation="vertical" customprefix:layout_width="match_parent" customprefix:layout_height="match_parent"> + <include layout="@layout/newlayout3" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"/> + <Button customprefix:text="Button" customprefix:id="@+id/button2" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2.xml new file mode 100644 index 0000000..48d4790 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3-variation2.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:customprefix="http://schemas.android.com/apk/res/android" customprefix:id="@+id/newlinear" customprefix:orientation="vertical" customprefix:layout_width="match_parent" customprefix:layout_height="match_parent"> + <ImageView customprefix:id="@+id/android_logo" customprefix:layout_width="wrap_content" + customprefix:layout_height="wrap_content" customprefix:src="@drawable/android_button" customprefix:focusable="false" customprefix:clickable="false" customprefix:layout_weight="1.0" /> + <Button customprefix:text="Button" customprefix:id="@+id/button1" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button> + <Button customprefix:text="Button" customprefix:id="@+id/button2" customprefix:layout_width="wrap_content" customprefix:layout_height="wrap_content"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3.info new file mode 100644 index 0000000..ef3324e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3.info @@ -0,0 +1,5 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.Button [0,0,49,36] <Button> + android.widget.ImageView [0,36,128,248] <ImageView> + android.widget.Button [0,248,49,284] <Button> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3.xml new file mode 100644 index 0000000..ddd136c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample3.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/newlinear" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <ImageView android:id="@+id/android_logo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_button" android:focusable="false" android:clickable="false" android:layout_weight="1.0" /> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4-expected-changeLayout4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4-expected-changeLayout4.xml new file mode 100644 index 0000000..2dd7ab7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4-expected-changeLayout4.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout android:id="@+id/RelativeLayout1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <CheckBox android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="CheckBox" android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content"></CheckBox> + <Button android:layout_below="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button5" android:layout_alignParentRight="true"></Button> +</RelativeLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4.info new file mode 100644 index 0000000..605c177 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4.info @@ -0,0 +1,5 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.CheckBox [0,0,80,36] <CheckBox> + android.widget.RelativeLayout [0,36,240,284] <RelativeLayout> + android.widget.Button [191,0,240,36] <Button> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4.xml new file mode 100644 index 0000000..a56f272 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample4.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <CheckBox android:text="CheckBox" android:id="@+id/checkBox1" android:layout_width="wrap_content" android:layout_height="wrap_content"></CheckBox> + <RelativeLayout android:layout_height="match_parent" android:id="@+id/relativeLayout1" android:layout_width="match_parent"> + <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button5" android:layout_alignParentRight="true"></Button> + </RelativeLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5-expected-changeLayout5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5-expected-changeLayout5.xml new file mode 100644 index 0000000..225476c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5-expected-changeLayout5.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout android:id="@+id/RelativeLayout1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:layout_centerHorizontal="true" android:layout_alignParentTop="true" android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"></Button> + <Button android:layout_centerHorizontal="true" android:layout_below="@+id/button1" android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"></Button> + <Button android:layout_alignParentRight="true" android:layout_below="@+id/button2" android:text="Button" android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right"></Button> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button4" android:layout_below="@+id/button3" android:layout_marginTop="70dp" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Button" android:id="@+id/button4" android:layout_width="wrap_content"></Button> + <Button android:layout_toRightOf="@+id/button4" android:layout_alignBaseline="@+id/button4" android:layout_alignTop="@+id/button4" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="Button" android:id="@+id/button5" android:layout_width="wrap_content"></Button> + <Button android:layout_toRightOf="@+id/button5" android:layout_alignParentBottom="true" android:layout_height="wrap_content" android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_gravity="bottom"></Button> +</RelativeLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5.info new file mode 100644 index 0000000..fce532e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5.info @@ -0,0 +1,9 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.Button [95,0,144,36] <Button> + android.widget.Button [95,36,144,72] <Button> + android.widget.Button [191,72,240,108] <Button> + android.widget.LinearLayout [0,108,240,284] <LinearLayout> + android.widget.Button [0,70,49,106] <Button> + android.widget.Button [49,70,98,106] <Button> + android.widget.Button [98,140,147,176] <Button> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5.xml new file mode 100644 index 0000000..afc1938 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample5.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal"></Button> + <Button android:text="Button" android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"></Button> + <Button android:text="Button" android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right"></Button> + <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="match_parent"> + <Button android:layout_height="wrap_content" android:layout_gravity="center" android:text="Button" android:id="@+id/button4" android:layout_width="wrap_content"></Button> + <Button android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="Button" android:id="@+id/button5" android:layout_width="wrap_content"></Button> + <Button android:layout_height="wrap_content" android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_gravity="bottom"></Button> + </LinearLayout> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6-expected-changeLayout6.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6-expected-changeLayout6.xml new file mode 100644 index 0000000..045d2ce --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6-expected-changeLayout6.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:baselineAligned="true"> + <Button android:layout_alignParentLeft="true" android:layout_alignBaseline="@+id/button2" android:layout_alignParentTop="true" android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <RadioButton android:layout_toRightOf="@+id/button1" android:layout_alignBaseline="@+id/button2" android:layout_alignParentTop="true" android:text="RadioButton" android:id="@+id/radioButton1" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton> + <Button android:layout_toRightOf="@+id/radioButton1" android:layout_alignBaseline="@+id/button2" android:layout_alignParentTop="true" android:layout_width="wrap_content" android:id="@+id/button2" android:text="Button" android:layout_height="150dip"></Button> +</RelativeLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6.info new file mode 100644 index 0000000..7a7a44a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6.info @@ -0,0 +1,5 @@ +android.widget.LinearLayout [0,36,240,320] <LinearLayout> + android.widget.Button [0,38,49,74] <Button> + android.widget.RadioButton [49,36,143,72] <RadioButton> + android.widget.Button [143,0,192,113] <Button> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6.xml new file mode 100644 index 0000000..5cdc824 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample6.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:baselineAligned="true"> + <Button android:text="Button" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <RadioButton android:text="RadioButton" android:id="@+id/radioButton1" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton> + <Button android:layout_width="wrap_content" android:id="@+id/button2" android:text="Button" android:layout_height="150dip"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7-expected-extract6.diff new file mode 100644 index 0000000..636e301 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7-expected-extract6.diff @@ -0,0 +1,10 @@ +< <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:layout_width="match_parent"> +< <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout5" android:layout_width="wrap_content"> +< <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout6" android:layout_width="wrap_content"> +< <!-- Comment --> +< <Button android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> +< </LinearLayout> +< </LinearLayout> +< </LinearLayout> +--- +> <include layout="@layout/newlayout6" android:id="@+id/linearLayout4_ref" android:layout_width="match_parent" android:layout_height="wrap_content"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7.info new file mode 100644 index 0000000..134234c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7.info @@ -0,0 +1,11 @@ +android.widget.LinearLayout [0,0,240,320] <LinearLayout> + android.widget.Button [0,0,101,36] <Button> + android.widget.LinearLayout [0,36,240,72] <LinearLayout> + android.widget.LinearLayout [0,0,73,36] <LinearLayout> + android.widget.LinearLayout [0,0,73,36] <LinearLayout> + android.widget.Button [0,0,73,36] <Button> + android.widget.LinearLayout [0,72,240,192] <LinearLayout> + android.widget.Button [0,0,117,36] <Button> + android.widget.Button [117,0,223,36] <Button> + android.widget.CheckBox [223,10,240,130] <CheckBox> + android.widget.Button [0,192,240,228] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7.xml new file mode 100644 index 0000000..0445bbf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample7.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout android:id="@+id/LinearLayout2" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> + <Button android:text="FirstButton" android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:layout_width="match_parent"> + <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout5" android:layout_width="wrap_content"> + <LinearLayout android:layout_height="match_parent" android:id="@+id/linearLayout6" android:layout_width="wrap_content"> + <!-- Comment --> + <Button android:text="Button" android:id="@+id/button6" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + </LinearLayout> + </LinearLayout> + </LinearLayout> + <LinearLayout android:layout_width="match_parent" android:id="@+id/linearLayout9" android:layout_height="wrap_content"> + <Button android:text="SecondButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button2"></Button> + <Button android:text="ThirdButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/button3"></Button> + <CheckBox android:id="@+id/checkBox1" android:text="CheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content"></CheckBox> + </LinearLayout> + <Button android:layout_height="wrap_content" android:text="FourthButton" android:id="@+id/button4" android:layout_width="match_parent"></Button> +</LinearLayout> + diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8-expected-extract6.diff b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8-expected-extract6.diff new file mode 100644 index 0000000..024141a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8-expected-extract6.diff @@ -0,0 +1,15 @@ +< <LinearLayout android:layout_height="wrap_content" +< android:id="@+id/linearLayout4" android:layout_width="match_parent"> +< <LinearLayout android:layout_height="match_parent" +< android:layout_width="wrap_content" android:id="@+id/linearLayout5"> +< <LinearLayout android:layout_height="match_parent" +< android:id="@+id/linearLayout6" +< android:layout_width="wrap_content"> +< <Button android:text="Button" android:id="@+id/button6" +< android:layout_width="wrap_content" +< android:layout_height="wrap_content"></Button> +< </LinearLayout> +< </LinearLayout> +< </LinearLayout> +--- +> <include layout="@layout/newlayout6" android:id="@+id/linearLayout4_ref" android:layout_width="match_parent" android:layout_height="wrap_content"/> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8.info new file mode 100644 index 0000000..ca294ba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8.info @@ -0,0 +1,12 @@ +android.widget.LinearLayout [0,0,240,320] <LinearLayout> + android.widget.Button [0,0,101,36] <Button> + android.widget.FrameLayout [0,36,240,72] <FrameLayout> + android.widget.LinearLayout [0,0,240,36] <LinearLayout> + android.widget.LinearLayout [0,0,73,36] <LinearLayout> + android.widget.LinearLayout [0,0,73,36] <LinearLayout> + android.widget.Button [0,0,73,36] <Button> + android.widget.FrameLayout [0,72,240,108] <FrameLayout> + android.widget.LinearLayout [0,0,240,36] <LinearLayout> + android.widget.LinearLayout [0,0,73,36] <LinearLayout> + android.widget.LinearLayout [0,0,73,36] <LinearLayout> + android.widget.Button [0,0,73,36] <Button> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8.xml new file mode 100644 index 0000000..c798469 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample8.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout android:id="@+id/LinearLayout2" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" android:layout_height="match_parent" + android:orientation="vertical"> + <Button android:text="FirstButton" android:id="@+id/button1" + android:layout_width="wrap_content" android:layout_height="wrap_content"></Button> + <FrameLayout android:id="@+id/outer" + android:layout_width="match_parent" android:layout_height="wrap_content"> + <LinearLayout android:layout_height="wrap_content" + android:id="@+id/linearLayout4" android:layout_width="match_parent"> + <LinearLayout android:layout_height="match_parent" + android:layout_width="wrap_content" android:id="@+id/linearLayout5"> + <LinearLayout android:layout_height="match_parent" + android:id="@+id/linearLayout6" + android:layout_width="wrap_content"> + <Button android:text="Button" android:id="@+id/button6" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></Button> + </LinearLayout> + </LinearLayout> + </LinearLayout> + </FrameLayout> + <FrameLayout android:id="@+id/outer" + android:layout_width="match_parent" android:layout_height="wrap_content"> + <LinearLayout android:layout_height="wrap_content" + android:id="@+id/linearLayout4" android:layout_width="match_parent"> + <LinearLayout android:layout_height="match_parent" + android:layout_width="wrap_content" android:id="@+id/linearLayout5"> + <LinearLayout android:layout_height="match_parent" + android:id="@+id/linearLayout6" + android:layout_width="wrap_content"> + <Button android:text="Button" android:id="@+id/button6" + android:layout_width="wrap_content" + android:layout_height="wrap_content"></Button> + </LinearLayout> + </LinearLayout> + </LinearLayout> + </FrameLayout> +</LinearLayout> diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java new file mode 100644 index 0000000..5eba812 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfoTest.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.manifest; + +import static com.android.resources.ScreenSize.LARGE; +import static com.android.resources.ScreenSize.NORMAL; +import static com.android.resources.ScreenSize.XLARGE; + +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; +import com.android.ide.eclipse.adt.internal.resources.ResourceHelper; +import com.android.sdklib.AndroidVersion; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.NullProgressMonitor; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Map; + +public class ManifestInfoTest extends AdtProjectTest { + @Override + protected boolean testCaseNeedsUniqueProject() { + return true; + } + + public void testGetActivityThemes1() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='4'/>\n" + + "</manifest>\n"); + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 0, map.size()); + assertEquals("com.android.unittest", info.getPackage()); + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, NORMAL))); + assertEquals("@android:style/Theme", info.getDefaultTheme(null, null)); + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, XLARGE))); + } + + public void testGetActivityThemes2() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='11'/>\n" + + "</manifest>\n"); + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 0, map.size()); + assertEquals("com.android.unittest", info.getPackage()); + assertEquals("Theme.Holo", ResourceHelper.styleToTheme(info.getDefaultTheme(null, + XLARGE))); + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, LARGE))); + } + + public void testGetActivityThemes3() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <uses-sdk android:minSdkVersion='11'/>\n" + + "</manifest>\n"); + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 0, map.size()); + assertEquals("com.android.unittest", info.getPackage()); + assertEquals("Theme.Holo", ResourceHelper.styleToTheme(info.getDefaultTheme(null, + XLARGE))); + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, NORMAL))); + } + + public void testGetActivityThemes4() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <application\n" + + " android:label='@string/app_name'\n" + + " android:name='.app.TestApp' android:icon='@drawable/app_icon'>\n" + + "\n" + + " <activity\n" + + " android:name='.prefs.PrefsActivity'\n" + + " android:label='@string/prefs_title' />\n" + + "\n" + + " <activity\n" + + " android:name='.app.IntroActivity'\n" + + " android:label='@string/intro_title'\n" + + " android:theme='@android:style/Theme.Dialog' />\n" + + " </application>\n" + + " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='4'/>\n" + + "</manifest>\n" + + "" + ); + assertEquals("com.android.unittest", info.getPackage()); + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, XLARGE))); + + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 1, map.size()); + assertNull(map.get("com.android.unittest.prefs.PrefsActivity")); + assertEquals("@android:style/Theme.Dialog", + map.get("com.android.unittest.app.IntroActivity")); + } + + public void testGetActivityThemes5() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'" + + " android:theme='@style/NoBackground'>\n" + + " <application\n" + + " android:label='@string/app_name'\n" + + " android:name='.app.TestApp' android:icon='@drawable/app_icon'>\n" + + "\n" + + " <activity\n" + + " android:name='.prefs.PrefsActivity'\n" + + " android:label='@string/prefs_title' />\n" + + "\n" + + " <activity\n" + + " android:name='.app.IntroActivity'\n" + + " android:label='@string/intro_title'\n" + + " android:theme='@android:style/Theme.Dialog' />\n" + + " </application>\n" + + " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='4'/>\n" + + "</manifest>\n" + + "" + ); + + assertEquals("@style/NoBackground", info.getDefaultTheme(null, XLARGE)); + assertEquals("@style/NoBackground", info.getDefaultTheme(null, NORMAL)); + assertEquals("NoBackground", ResourceHelper.styleToTheme(info.getDefaultTheme(null, + NORMAL))); + + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 1, map.size()); + assertNull(map.get("com.android.unittest.prefs.PrefsActivity")); + assertEquals("@android:style/Theme.Dialog", + map.get("com.android.unittest.app.IntroActivity")); + } + + public void testGetActivityThemes6() throws Exception { + // Ensures that when the *rendering* target is less than version 11, we don't + // use Holo even though the manifest SDK version calls for it. + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='11'/>\n" + + "</manifest>\n"); + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 0, map.size()); + assertEquals("com.android.unittest", info.getPackage()); + assertEquals("Theme.Holo", ResourceHelper.styleToTheme(info.getDefaultTheme(null, + XLARGE))); + + // Here's the check + IAndroidTarget olderVersion = new TestAndroidTarget(4); + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(olderVersion, + XLARGE))); + + } + + public void testGetApplicationLabelAndIcon() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <application android:icon=\"@drawable/icon\"\n" + + " android:label=\"@string/app_name\">\n" + + " </application>\n" + + "" + + "</manifest>\n"); + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 0, map.size()); + assertEquals("com.android.unittest", info.getPackage()); + + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, NORMAL))); + assertEquals("@drawable/icon", info.getApplicationIcon()); + assertEquals("@string/app_name", info.getApplicationLabel()); + } + + public void testGetApplicationNoLabelOrIcon() throws Exception { + ManifestInfo info = getManifestInfo( + "<manifest xmlns:android='http://schemas.android.com/apk/res/android'\n" + + " package='com.android.unittest'>\n" + + " <application>\n" + + " </application>\n" + + "" + + "</manifest>\n"); + Map<String, String> map = info.getActivityThemes(); + assertEquals(map.toString(), 0, map.size()); + assertEquals("com.android.unittest", info.getPackage()); + + assertEquals("Theme", ResourceHelper.styleToTheme(info.getDefaultTheme(null, NORMAL))); + assertNull(info.getApplicationIcon()); + assertNull(info.getApplicationLabel()); + } + + private ManifestInfo getManifestInfo(String manifestContents) throws Exception { + InputStream bstream = new ByteArrayInputStream( + manifestContents.getBytes("UTF-8")); //$NON-NLS-1$ + + IFile file = getProject().getFile("AndroidManifest.xml"); + if (file.exists()) { + file.setContents(bstream, IFile.FORCE, new NullProgressMonitor()); + } else { + file.create(bstream, false /* force */, new NullProgressMonitor()); + } + return ManifestInfo.get(getProject()); + } + + private static class TestAndroidTarget implements IAndroidTarget { + private final int mApiLevel; + + public TestAndroidTarget(int apiLevel) { + mApiLevel = apiLevel; + } + + public boolean canRunOn(IAndroidTarget target) { + return false; + } + + public String[] getAbiList() { + return null; + } + + public String getClasspathName() { + return null; + } + + public String getDefaultSkin() { + return null; + } + + public String getDescription() { + return null; + } + + public String getFullName() { + return null; + } + + public String getImagePath(String abiType) { + return null; + } + + public String getLocation() { + return null; + } + + public String getName() { + return null; + } + + public IOptionalLibrary[] getOptionalLibraries() { + return null; + } + + public IAndroidTarget getParent() { + return null; + } + + public String getPath(int pathId) { + return null; + } + + public String[] getPlatformLibraries() { + return null; + } + + public Map<String, String> getProperties() { + return null; + } + + public String getProperty(String name) { + return null; + } + + public Integer getProperty(String name, Integer defaultValue) { + return null; + } + + public Boolean getProperty(String name, Boolean defaultValue) { + return null; + } + + public int getRevision() { + return 0; + } + + public String[] getSkins() { + return null; + } + + public int getUsbVendorId() { + return 0; + } + + public String getVendor() { + return null; + } + + public AndroidVersion getVersion() { + return new AndroidVersion(mApiLevel, null); + } + + public String getVersionName() { + return null; + } + + public String hashString() { + return null; + } + + public boolean isPlatform() { + return false; + } + + public int compareTo(IAndroidTarget o) { + return 0; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/xml/HyperlinksTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/xml/HyperlinksTest.java new file mode 100644 index 0000000..57ca80f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/xml/HyperlinksTest.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2010 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 com.android.ide.eclipse.adt.internal.editors.xml; + +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest; +import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks.ResourceLink; +import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks.XmlResolver; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.hyperlink.IHyperlink; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.internal.ErrorEditorPart; +import org.eclipse.ui.internal.browser.WebBrowserEditor; +import org.eclipse.wst.sse.ui.StructuredTextEditor; +import org.eclipse.wst.sse.ui.internal.StructuredTextViewer; +import org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +@SuppressWarnings("restriction") +public class HyperlinksTest extends AdtProjectTest { + @Override + protected boolean testCaseNeedsUniqueProject() { + return true; + } + + public void testFqnRegexp() throws Exception { + assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo").matches()); + assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.pk_g.Foo_Bar1"). + matches()); + assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo$Inner").matches()); + + // Should we allow non-standard packages and class names? + // For now, we're allowing it -- see how this works out in practice. + //assertFalse(XmlHyperlinkResolver.CLASS_PATTERN.matcher("Foo.bar").matches()); + assertTrue(Hyperlinks.CLASS_PATTERN.matcher("Foo.bar").matches()); + + assertFalse(Hyperlinks.CLASS_PATTERN.matcher("LinearLayout").matches()); + assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".").matches()); + assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".F").matches()); + assertFalse(Hyperlinks.CLASS_PATTERN.matcher("f.").matches()); + assertFalse(Hyperlinks.CLASS_PATTERN.matcher("Foo").matches()); + assertFalse(Hyperlinks.CLASS_PATTERN.matcher("com.android.1Foo").matches()); + assertFalse(Hyperlinks.CLASS_PATTERN.matcher("1com.Foo").matches()); + } + + public void testNavigate1() throws Exception { + // Check navigating to a local resource + checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", + "android:text=\"@string/app^_name\""); + } + + public void testNavigate2() throws Exception { + // Check navigating to a framework resource + checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", + "marginLeft=\"@android:dimen/app_ico^n_size\""); + } + + public void testNavigate3() throws Exception { + // Check navigating to a style + checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", + "style=\"@android:style/Widget.B^utton\""); + } + + public void testNavigate4() throws Exception { + // Check navigating to resource with many resolutions + checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", + "android:text=\"@android:st^ring/ok\""); + } + + public void testNavigate5() throws Exception { + // Check navigating to styles + checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", + "parent=\"android:Theme.Li^ght\">"); + } + + public void testNavigate6() throws Exception { + // Check navigating to a portion of a style (this should pick android:Theme, not + // android:Theme.Light + checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", + "parent=\"android:The^me.Light\">"); + } + + public void testNavigate7() throws Exception { + // Check navigating to a resource inside text content + checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", + "popupBackground\">@android:drawable/spinner_dr^opdown_background</item>"); + } + + public void testNavigate8() throws Exception { + // Check navigating to a resource inside text content where there is space around + // the URL + checkXmlNavigation("navigationstyles.xml", "res/values/navigationstyles.xml", + "colorBackground\"> @color/cust^om_theme_color </item>"); + } + + public void testNavigate9a() throws Exception { + // Check navigating to a an activity + checkXmlNavigation("manifest.xml", "AndroidManifest.xml", + "<activity android:name=\".Test^Activity\""); + } + + /* Not yet implemented + public void testNavigate9b() throws Exception { + // Check navigating to a an activity - clicking on the activity element should + // work too + checkXmlNavigation("manifest.xml", "AndroidManifest.xml", + "<acti^vity android:name=\".TestActivity\""); + } + */ + + public void testNavigate10() throws Exception { + // Check navigating to a permission + checkXmlNavigation("manifest.xml", "AndroidManifest.xml", + "<uses-permission android:name=\"android.permission.AC^CESS_NETWORK_STATE\" />"); + } + + public void testNavigate11a() throws Exception { + // Check navigating to an intent + checkXmlNavigation("manifest.xml", "AndroidManifest.xml", + "<action android:name=\"android.intent.ac^tion.MAIN\" />"); + } + + public void testNavigate11g() throws Exception { + // Check navigating to an intent + checkXmlNavigation("manifest.xml", "AndroidManifest.xml", + "<category android:name=\"android.intent.category.LA^UNCHER\" />"); + } + + public void testNavigate12() throws Exception { + // Check navigating to a custom view class + checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml", + "<my.Cust^omView></my.CustomView>"); + } + + // Left to test: + // onClick handling + // class attributes + // Test that the correct file is actually opened! + + private void checkXmlNavigation(String basename, String targetPath, + String caretLocation) throws Exception { + IFile file = getTestDataFile(getProject(), basename, targetPath, true); + + // Determine the offset + int offset = getCaretOffset(file, caretLocation); + + // Open file + IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + assertNotNull(page); + IEditorPart editor = IDE.openEditor(page, file); + assertTrue(editor.getClass().getName(), editor instanceof AndroidXmlEditor); + AndroidXmlEditor layoutEditor = (AndroidXmlEditor) editor; + ISourceViewer viewer = layoutEditor.getStructuredSourceViewer(); + + XmlResolver resolver = new Hyperlinks.XmlResolver(); + IHyperlink[] links = resolver.detectHyperlinks(viewer, new Region(offset, 0), true); + assertNotNull(links); + + StringBuilder sb = new StringBuilder(1000); + sb.append("Go To Declaration in " + basename + " for " + caretLocation + ":\n"); + for (IHyperlink link : links) { + sb.append(link.getHyperlinkText()); + sb.append(" : "); + sb.append(" ["); + IRegion region = link.getHyperlinkRegion(); + sb.append(viewer.getDocument().get(region.getOffset(), region.getLength())); + sb.append("]"); + if (link instanceof Hyperlinks.ResourceLink) { + Hyperlinks.ResourceLink resourceLink = (ResourceLink) link; + sb.append("\n "); + ResourceFile resourceFile = resourceLink.getFile(); + String desc = resourceFile.toString(); + desc = desc.replace('\\', '/'); + // For files in the SDK folder, strip out file prefix + int dataRes = desc.indexOf("data/res"); + if (dataRes != -1) { + desc = desc.substring(dataRes); + } + desc = removeSessionData(desc); + sb.append(desc); + } + sb.append('\n'); + } + + // Open the first link + IHyperlink link = links[0]; + link.open(); + IEditorPart newEditor = Hyperlinks.getEditor(); + // Ensure that this isn't an invalid file (e.g. opening the SDK platform files + // with incorrect content binding could cause this) + assertTrue(!(newEditor instanceof ErrorEditorPart)); + + IDocument document = null; + Point selection = null; + + if (newEditor instanceof AndroidXmlEditor) { + AndroidXmlEditor xmlEditor = (AndroidXmlEditor) newEditor; + document = xmlEditor.getStructuredSourceViewer().getDocument(); + selection = xmlEditor.getStructuredSourceViewer().getSelectedRange(); + } else if (newEditor instanceof XMLMultiPageEditorPart) { + XMLMultiPageEditorPart xmlEditor = (XMLMultiPageEditorPart) newEditor; + Field field = xmlEditor.getClass().getDeclaredField("fTextEditor"); + field.setAccessible(true); + StructuredTextEditor ste = (StructuredTextEditor) field.get(newEditor); + if (ste == null) { + Method method = xmlEditor.getClass().getMethod("getTextEditor", new Class[0]); + ste = (StructuredTextEditor) method.invoke(newEditor, new Object[0]); + } + StructuredTextViewer textViewer = ste.getTextViewer(); + document = textViewer.getDocument(); + selection = textViewer.getSelectedRange(); + } else if (newEditor instanceof WebBrowserEditor) { + WebBrowserEditor browser = (WebBrowserEditor) newEditor; + Field field = browser.getClass().getDeclaredField("initialURL"); + field.setAccessible(true); + String initialUrl = (String) field.get(newEditor); + int index = initialUrl.indexOf("reference"); + if (index != -1) { + initialUrl = initialUrl.substring(index); + } + initialUrl = initialUrl.replace('\\', '/'); + sb.append("\n\nAfter open, a browser is shown with this URL:\n"); + sb.append(" "); + sb.append(initialUrl); + sb.append("\n"); + } else { + fail("Unhandled editor type: " + newEditor.getClass().getName()); + return; + } + + if (document != null && selection != null) { + int lineStart = document.getLineInformationOfOffset(selection.x).getOffset(); + IRegion lineEndInfo = document.getLineInformationOfOffset(selection.x + selection.y); + int lineEnd = lineEndInfo.getOffset() + lineEndInfo.getLength(); + String text = document.get(lineStart, lineEnd - lineStart); + int selectionStart = selection.x - lineStart; + int selectionEnd = selectionStart + selection.y; + if (selectionEnd > selectionStart) { + // Selection range + text = text.substring(0, selectionStart) + "[^" + + text.substring(selectionStart, selectionEnd) + "]" + + text.substring(selectionEnd); + } else { + text = text.substring(0, selectionStart) + "^" + + text.substring(selectionStart); + } + text = removeSessionData(text); + + sb.append("\n\nAfter open, the selected text is:\n"); + sb.append(text); + sb.append("\n"); + } + + assertEqualsGolden(basename, sb.toString(), "txt"); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java index 0ed9ed4..f524d7f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubProjectCreationPage.java @@ -1,12 +1,12 @@ /* * 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 @@ -20,6 +20,7 @@ import com.android.sdklib.IAndroidTarget; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; +import org.eclipse.ui.IWorkingSet; /** * Stub class for project creation page. @@ -87,6 +88,10 @@ public class StubProjectCreationPage extends NewProjectCreationPage { public boolean useDefaultLocation() { return false; } + + public IWorkingSet[] getSelectedWorkingSets() { + return new IWorkingSet[0]; + } }; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java index 281170e..b4420e4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java @@ -15,7 +15,9 @@ */ package com.android.ide.eclipse.tests; -import com.android.ide.eclipse.adt.AndroidConstants; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.sdklib.SdkConstants; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Platform; @@ -36,6 +38,10 @@ public class AdtTestData { private static AdtTestData sInstance = null; private static final Logger sLogger = Logger.getLogger(AdtTestData.class.getName()); + /** The prefered directory separator to use. */ + private static final String DIR_SEP_STR = "/"; + private static final char DIR_SEP_CHAR = '/'; + /** The absolute file path to the plugin's contents. */ private String mOsRootDataPath; @@ -54,6 +60,20 @@ public class AdtTestData { sLogger.info("Running as an Eclipse Plug-in JUnit test, using FileLocator"); try { mOsRootDataPath = FileLocator.resolve(url).getFile(); + if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) { + // Fix the path returned by the URL resolver + // so that it actually works on Windows. + + // First, Windows paths don't start with a / especially + // if they contain a drive spec such as C:/... + int pos = mOsRootDataPath.indexOf(':'); + if (pos > 0 && mOsRootDataPath.charAt(0) == '/') { + mOsRootDataPath = mOsRootDataPath.substring(1); + } + + // Looking for "." probably inserted a /./, so clean it up + mOsRootDataPath = mOsRootDataPath.replace("/./", "/"); + } } catch (IOException e) { sLogger.warning("IOException while using FileLocator, reverting to url"); mOsRootDataPath = url.getFile(); @@ -64,13 +84,18 @@ public class AdtTestData { } } - if (mOsRootDataPath.equals(AndroidConstants.WS_SEP)) { + if (mOsRootDataPath.equals(AdtConstants.WS_SEP)) { sLogger.warning("Resource data not found using class loader!, Defaulting to no path"); } - if (!mOsRootDataPath.endsWith(File.separator)) { + if (File.separatorChar == '\\') { + // On Windows, uniformize all separators to use the / convention + mOsRootDataPath.replace('\\', DIR_SEP_CHAR); + } + + if (!mOsRootDataPath.endsWith(File.separator) && !mOsRootDataPath.endsWith(DIR_SEP_STR)) { sLogger.info("Fixing test_data env variable (does not end with path separator)"); - mOsRootDataPath = mOsRootDataPath.concat(File.separator); + mOsRootDataPath += DIR_SEP_STR; } } @@ -91,6 +116,20 @@ public class AdtTestData { * @return absolute OS path to test file */ public String getTestFilePath(String osRelativePath) { - return mOsRootDataPath + osRelativePath; + File path = new File(mOsRootDataPath, osRelativePath); + + if (!path.exists()) { + // On Windows at least this ends up using the wrong plugin path. + String pkgAdt = AdtPlugin .class.getPackage().getName(); + String pkgTests = AdtTestData.class.getPackage().getName(); + + if (mOsRootDataPath.contains(pkgAdt)) { + path = new File(mOsRootDataPath.replace(pkgAdt, pkgTests), osRelativePath); + } + + assert path.exists(); + } + + return path.getAbsolutePath(); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkTestCase.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkTestCase.java index 2774557..d8692ae 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkTestCase.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/SdkTestCase.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.tests; import com.android.ide.common.sdk.LoadStatus; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetParser; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.sdklib.IAndroidTarget; @@ -60,6 +61,13 @@ public abstract class SdkTestCase extends TestCase { return null; } + // We'll never break out of the SDK load-wait-loop if the AdtPlugin doesn't + // actually have a valid SDK location because it won't have started an async load: + String sdkLocation = AdtPrefs.getPrefs().getOsSdkFolder(); + assertTrue("No valid SDK installation is set; for tests you typically need to set the" + + " environment variable ADT_TEST_SDK_PATH to point to an SDK folder", + sdkLocation != null && sdkLocation.length() > 0); + Object sdkLock = Sdk.getLock(); LoadStatus loadStatus = LoadStatus.LOADING; // wait for ADT to load the SDK on a separate thread @@ -85,6 +93,10 @@ public abstract class SdkTestCase extends TestCase { return sdk; } + protected boolean validateSdk(IAndroidTarget target) { + return true; + } + /** * Checks that the provided sdk contains one or more valid targets. * @param sdk the {@link Sdk} to validate. @@ -92,6 +104,9 @@ public abstract class SdkTestCase extends TestCase { private void validateSdk(Sdk sdk) { assertTrue("sdk has no targets", sdk.getTargets().length > 0); for (IAndroidTarget target : sdk.getTargets()) { + if (!validateSdk(target)) { + continue; + } IStatus status = new AndroidTargetParser(target).run(new NullProgressMonitor()); if (status.getCode() != IStatus.OK) { fail("Failed to parse targets data"); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java index f18ec36..8b71c5a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/layoutRendering/ApiDemosRenderingTest.java @@ -16,29 +16,34 @@ package com.android.ide.eclipse.tests.functests.layoutRendering; +import com.android.AndroidConstants; import com.android.ide.common.rendering.LayoutLibrary; +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.RenderSession; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.ide.common.resources.ResourceRepository; import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +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.sdk.LoadStatus; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenRatioQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.tests.SdkTestCase; +import com.android.io.FolderWrapper; import com.android.resources.Density; import com.android.resources.Keyboard; import com.android.resources.KeyboardState; @@ -50,7 +55,6 @@ import com.android.resources.ScreenSize; import com.android.resources.TouchScreen; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.FolderWrapper; import com.android.util.Pair; import org.kxml2.io.KXmlParser; @@ -125,6 +129,21 @@ public class ApiDemosRenderingTest extends SdkTestCase { return null; } + public ILayoutPullParser getParser(String layoutName) { + return null; + } + + public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, + ResourceReference itemRef, int fullPosition, int typePosition, + int fullChildPosition, int typeChildPosition, + ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue) { + return null; + } + + public AdapterBinding getAdapterBinding(ResourceReference adapterView, + Object adapterCookie, Object viewObject) { + return null; + } } public void testApiDemos() throws IOException, XmlPullParserException { @@ -168,13 +187,13 @@ public class ApiDemosRenderingTest extends SdkTestCase { } // look for the layout folder - File layoutFolder = new File(resFolder, SdkConstants.FD_LAYOUT); + File layoutFolder = new File(resFolder, AndroidConstants.FD_RES_LAYOUT); if (layoutFolder.isDirectory() == false) { fail("Sample project has no layout folder!"); } // first load the project's target framework resource - ProjectResources framework = ResourceManager.getInstance().loadFrameworkResources(target); + ResourceRepository framework = ResourceManager.getInstance().loadFrameworkResources(target); // now load the project resources ProjectResources project = new ProjectResources(null /*project*/); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java index f4092eb..62725b8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/AbsoluteLayoutRuleTest.java @@ -60,8 +60,8 @@ public class AbsoluteLayoutRuleTest extends LayoutTestBase { // Drop preview "useStyle(DROP_PREVIEW), drawRect(Rect[30,-10,105,80])"); - assertEquals("30dip", inserted.getStringAttr(ANDROID_URI, "layout_x")); - assertEquals("-10dip", inserted.getStringAttr(ANDROID_URI, "layout_y")); + assertEquals("30dp", inserted.getStringAttr(ANDROID_URI, "layout_x")); + assertEquals("-10dp", inserted.getStringAttr(ANDROID_URI, "layout_y")); // Without drag bounds we should just draw guide lines instead inserted = dragInto(new Rect(0, 0, 0, 0), new Point(30, -10), 4, -1, @@ -70,8 +70,8 @@ public class AbsoluteLayoutRuleTest extends LayoutTestBase { "useStyle(GUIDELINE), drawLine(30,0,30,480), drawLine(0,-10,240,-10)", // Drop preview "useStyle(DROP_PREVIEW), drawLine(30,-10,240,-10), drawLine(30,-10,30,480)"); - assertEquals("30dip", inserted.getStringAttr(ANDROID_URI, "layout_x")); - assertEquals("-10dip", inserted.getStringAttr(ANDROID_URI, "layout_y")); + assertEquals("30dp", inserted.getStringAttr(ANDROID_URI, "layout_x")); + assertEquals("-10dp", inserted.getStringAttr(ANDROID_URI, "layout_y")); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java index 5b79ac3..18d985e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java @@ -39,7 +39,7 @@ import junit.framework.TestCase; /** * Common layout helpers from LayoutRule tests */ -public abstract class LayoutTestBase extends TestCase { +public class LayoutTestBase extends TestCase { /** * Helper function used by tests to drag a button into a canvas containing * the given children. @@ -244,6 +244,15 @@ public abstract class LayoutTestBase extends TestCase { fail("Not supported in tests yet"); return null; } + + public String displayIncludeSourceInput() { + fail("Not supported in tests yet"); + return null; + } + + public void select(Collection<INode> nodes) { + fail("Not supported in tests yet"); + } } public void testDummy() { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java index 14430a5..d5f1ae9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestNode.java @@ -154,6 +154,13 @@ public class TestNode implements INode { return child; } + public void removeChild(INode node) { + int index = mChildren.indexOf(node); + if (index != -1) { + removeChild(index); + } + } + public boolean setAttribute(String uri, String localName, String value) { mAttributes.put(uri + localName, new TestAttribute(uri, localName, value)); return true; @@ -164,4 +171,5 @@ public class TestNode implements INode { return "TestNode [fqn=" + mFqcn + ", infos=" + mAttributeInfos + ", attributes=" + mAttributes + ", bounds=" + mBounds + "]"; } + }
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtilsTest.java index 5ccb494..580fbaa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtilsTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/descriptors/DescriptorsUtilsTest.java @@ -65,6 +65,7 @@ public class DescriptorsUtilsTest extends TestCase { assertEquals("Capital", DescriptorsUtils.capitalize("Capital")); assertEquals("CamelCase", DescriptorsUtils.capitalize("camelCase")); assertEquals("", DescriptorsUtils.capitalize("")); + assertSame("Foo", DescriptorsUtils.capitalize("Foo")); } public void testFormatTooltip() { @@ -240,15 +241,9 @@ public class DescriptorsUtilsTest extends TestCase { } } - public void testToXmlAttributeValue() throws Exception { - assertEquals("", DescriptorsUtils.toXmlAttributeValue("")); - assertEquals("foo", DescriptorsUtils.toXmlAttributeValue("foo")); - assertEquals("foo<bar", DescriptorsUtils.toXmlAttributeValue("foo<bar")); - - assertEquals(""", DescriptorsUtils.toXmlAttributeValue("\"")); - assertEquals("'", DescriptorsUtils.toXmlAttributeValue("'")); - assertEquals("foo"b''ar", - DescriptorsUtils.toXmlAttributeValue("foo\"b''ar")); + public void testGetBasename() { + assertEquals("Foo", DescriptorsUtils.getBasename("Foo")); + assertEquals("Foo", DescriptorsUtils.getBasename("foo.Foo")); + assertEquals("String", DescriptorsUtils.getBasename("java.util.String")); } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java index a240f90..2df472e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasViewInfoTest.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import com.android.ide.common.rendering.api.Capability; +import com.android.ide.common.rendering.api.DataBindingItem; import com.android.ide.common.rendering.api.MergeCookie; import com.android.ide.common.rendering.api.ViewInfo; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; @@ -40,7 +41,7 @@ import junit.framework.TestCase; public class CanvasViewInfoTest extends TestCase { - private static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) { + public static ViewElementDescriptor createDesc(String name, String fqn, boolean hasChildren) { if (hasChildren) { return new ViewElementDescriptor(name, name, fqn, "", "", new AttributeDescriptor[0], new AttributeDescriptor[0], new ElementDescriptor[1], false); @@ -49,7 +50,7 @@ public class CanvasViewInfoTest extends TestCase { } } - private static UiViewElementNode createNode(UiViewElementNode parent, String fqn, + public static UiViewElementNode createNode(UiViewElementNode parent, String fqn, boolean hasChildren) { String name = fqn.substring(fqn.lastIndexOf('.') + 1); ViewElementDescriptor descriptor = createDesc(name, fqn, hasChildren); @@ -60,11 +61,20 @@ public class CanvasViewInfoTest extends TestCase { return (UiViewElementNode) parent.appendNewUiChild(descriptor); } - private static UiViewElementNode createNode(String fqn, boolean hasChildren) { + public static UiViewElementNode createNode(String fqn, boolean hasChildren) { return createNode(null, fqn, hasChildren); } public void testNormalCreate() throws Exception { + normal(true); + } + + public void testNormalCreateLayoutLib5() throws Exception { + normal(false); + } + + private void normal(boolean layoutlib5) { + // Normal view hierarchy, no null keys anywhere UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); @@ -75,7 +85,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child2 = new ViewInfo("Button", child2Node, 0, 20, 70, 25); root.setChildren(Arrays.asList(child1, child2)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -100,6 +110,15 @@ public class CanvasViewInfoTest extends TestCase { } public void testShowIn() throws Exception { + showIn(false); + } + + public void testShowInLayoutLib5() throws Exception { + showIn(true); + } + + public void showIn(boolean layoutlib5) throws Exception { + // Test rendering of "Show Included In" (included content rendered // within an outer content that has null keys) @@ -112,7 +131,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", child21Node, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -137,6 +156,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testIncludeTag() throws Exception { + boolean layoutlib5 = true; + // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag) UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); @@ -149,7 +170,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -176,9 +197,10 @@ public class CanvasViewInfoTest extends TestCase { } public void testNoIncludeTag() throws Exception { + boolean layoutlib5 = false; + // Test rendering of included views on layoutlib 4- (e.g. no <include> tag cookie - // in - // view info) + // in view info) UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100); @@ -190,7 +212,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -217,6 +239,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMergeMatching() throws Exception { + boolean layoutlib5 = false; + // Test rendering of MULTIPLE included views or when there is no simple match // between view info and ui element node children @@ -232,7 +256,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -272,6 +296,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMerge() throws Exception { + boolean layoutlib5 = false; + // Test rendering of MULTIPLE included views or when there is no simple match // between view info and ui element node children @@ -286,7 +312,7 @@ public class CanvasViewInfoTest extends TestCase { ViewInfo child21 = new ViewInfo("RadioButton", null, 0, 20, 70, 25); child2.setChildren(Arrays.asList(child21)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); assertEquals(new Rectangle(10, 10, 89, 89), rootView.getAbsRect()); @@ -313,6 +339,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testInsertMerge() throws Exception { + boolean layoutlib5 = false; + // Test rendering of MULTIPLE included views or when there is no simple match // between view info and ui element node children @@ -320,7 +348,7 @@ public class CanvasViewInfoTest extends TestCase { UiViewElementNode rootNode = createNode(mergeNode, "android.widget.Button", false); ViewInfo root = new ViewInfo("Button", rootNode, 10, 10, 100, 100); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("merge", rootView.getName()); assertSame(rootView.getUiViewNode(), mergeNode); @@ -340,6 +368,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testUnmatchedMissing() throws Exception { + boolean layoutlib5 = false; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100); List<ViewInfo> children = new ArrayList<ViewInfo>(); @@ -387,7 +417,7 @@ public class CanvasViewInfoTest extends TestCase { } root.setChildren(children); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); // dump(root, 0); @@ -412,6 +442,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMergeCookies() throws Exception { + boolean layoutlib5 = true; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100); @@ -431,7 +463,7 @@ public class CanvasViewInfoTest extends TestCase { } root.setChildren(children); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("LinearLayout", rootView.getName()); @@ -446,6 +478,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testMergeCookies2() throws Exception { + boolean layoutlib5 = true; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("LinearLayout", rootNode, 0, 0, 100, 100); @@ -460,12 +494,13 @@ public class CanvasViewInfoTest extends TestCase { ArrayList<ViewInfo> children = new ArrayList<ViewInfo>(); for (int i = 0; i < 10; i++) { Object cookie = (i % 2) == 0 ? cookie1 : cookie2; - ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, (i + 1) * 20); + ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, + (i + 1) * 20); children.add(childView); } root.setChildren(children); - Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root); + Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5); CanvasViewInfo rootView = result.getFirst(); List<Rectangle> bounds = result.getSecond(); assertNull(bounds); @@ -495,6 +530,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testIncludeBounds() throws Exception { + boolean layoutlib5 = true; + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100); @@ -509,12 +546,13 @@ public class CanvasViewInfoTest extends TestCase { ArrayList<ViewInfo> children = new ArrayList<ViewInfo>(); for (int i = 0; i < 10; i++) { Object cookie = (i % 2) == 0 ? cookie1 : cookie2; - ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, (i + 1) * 20); + ViewInfo childView = new ViewInfo("childView" + i, cookie, 0, i * 20, 50, + (i + 1) * 20); children.add(childView); } root.setChildren(children); - Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root); + Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5); CanvasViewInfo rootView = result.getFirst(); List<Rectangle> bounds = result.getSecond(); assertNotNull(rootView); @@ -548,21 +586,27 @@ public class CanvasViewInfoTest extends TestCase { } public void testIncludeBounds2() throws Exception { + includeBounds2(false); + } + + public void testIncludeBounds2LayoutLib5() throws Exception { + includeBounds2(true); + } + + public void includeBounds2(boolean layoutlib5) throws Exception { + UiViewElementNode rootNode = createNode("android.widget.LinearLayout", true); ViewInfo root = new ViewInfo("included", null, 0, 0, 100, 100); UiViewElementNode node1 = createNode(rootNode, "childNode1", false); UiViewElementNode node2 = createNode(rootNode, "childNode2", false); - // Sets alternating merge cookies and checks whether the node sibling lists are - // okay and merged correctly - ViewInfo childView1 = new ViewInfo("childView1", node1, 0, 20, 50, 40); ViewInfo childView2 = new ViewInfo("childView2", node2, 0, 40, 50, 60); root.setChildren(Arrays.asList(childView1, childView2)); - Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root); + Pair<CanvasViewInfo, List<Rectangle>> result = CanvasViewInfo.create(root, layoutlib5); CanvasViewInfo rootView = result.getFirst(); List<Rectangle> bounds = result.getSecond(); assertNotNull(rootView); @@ -580,6 +624,8 @@ public class CanvasViewInfoTest extends TestCase { } public void testGestureOverlayView() throws Exception { + boolean layoutlib5 = true; + // Test rendering of included views on layoutlib 5+ (e.g. has <include> tag) UiViewElementNode rootNode = createNode("android.gesture.GestureOverlayView", true); @@ -590,7 +636,7 @@ public class CanvasViewInfoTest extends TestCase { root.setChildren(Collections.singletonList(child)); ViewInfo grandChild = new ViewInfo("Button", grandChildNode, 0, 20, 70, 25); child.setChildren(Collections.singletonList(grandChild)); - CanvasViewInfo rootView = CanvasViewInfo.create(root).getFirst(); + CanvasViewInfo rootView = CanvasViewInfo.create(root, layoutlib5).getFirst(); assertNotNull(rootView); assertEquals("GestureOverlayView", rootView.getName()); @@ -611,6 +657,46 @@ public class CanvasViewInfoTest extends TestCase { assertFalse(grandChildView.isRoot()); } + public void testListView() throws Exception { + // For ListViews we get AdapterItemReferences as cookies. Ensure that this + // works properly. + // + // android.widget.FrameLayout [0,50,320,480] <FrameLayout> + // android.widget.ListView [0,0,320,430] <ListView> + // android.widget.LinearLayout [0,0,320,17] SessionParams$AdapterItemReference + // android.widget.TextView [0,0,73,17] + // android.widget.LinearLayout [0,18,320,35] SessionParams$AdapterItemReference + // android.widget.TextView [0,0,73,17] + // android.widget.LinearLayout [0,36,320,53] SessionParams$AdapterItemReference + // android.widget.TextView [0,0,73,17] + // ... + + UiViewElementNode rootNode = createNode("FrameLayout", true); + UiViewElementNode childNode = createNode(rootNode, "ListView", false); + /*UiViewElementNode grandChildNode =*/ createNode(childNode, "LinearLayout", false); + /*UiViewElementNode greatGrandChildNode =*/ createNode(childNode, "TextView", false); + DataBindingItem dataBindingItem = new DataBindingItem("foo"); + + ViewInfo root = new ViewInfo("FrameLayout", rootNode, 0, 50, 320, 480); + ViewInfo child = new ViewInfo("ListView", childNode, 0, 0, 320, 430); + root.setChildren(Collections.singletonList(child)); + ViewInfo grandChild = new ViewInfo("LinearLayout", dataBindingItem, 0, 0, 320, 17); + child.setChildren(Collections.singletonList(grandChild)); + ViewInfo greatGrandChild = new ViewInfo("Button", null, 0, 0, 73, 17); + grandChild.setChildren(Collections.singletonList(greatGrandChild)); + CanvasViewInfo rootView = CanvasViewInfo.create(root, true /*layoutlib5*/).getFirst(); + assertNotNull(rootView); + + assertEquals("FrameLayout", rootView.getName()); + assertEquals(1, rootView.getChildren().size()); + assertSame(rootNode, rootView.getUiViewNode()); + + CanvasViewInfo childView = rootView.getChildren().get(0); + assertEquals("ListView", childView.getName()); + assertEquals(0, childView.getChildren().size()); + assertSame(childNode, childView.getUiViewNode()); + } + /** * Dumps out the given {@link ViewInfo} hierarchy to standard out. * Useful during development. diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilitiesTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilitiesTest.java new file mode 100644 index 0000000..049e1cc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilitiesTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import java.util.Arrays; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import junit.framework.TestCase; + +public class DomUtilitiesTest extends TestCase { + + public void testToXmlAttributeValue() throws Exception { + assertEquals("", DomUtilities.toXmlAttributeValue("")); + assertEquals("foo", DomUtilities.toXmlAttributeValue("foo")); + assertEquals("foo<bar", DomUtilities.toXmlAttributeValue("foo<bar")); + + assertEquals(""", DomUtilities.toXmlAttributeValue("\"")); + assertEquals("'", DomUtilities.toXmlAttributeValue("'")); + assertEquals("foo"b''ar", + DomUtilities.toXmlAttributeValue("foo\"b''ar")); + } + + public void testIsEquivalent() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document1 = builder.newDocument(); + Document document2 = builder.newDocument(); + document1.appendChild(document1.createElement("root")); + document2.appendChild(document2.createElement("root")); + + assertFalse(DomUtilities.isEquivalent(null, null)); + Element root1 = document1.getDocumentElement(); + assertFalse(DomUtilities.isEquivalent(null, root1)); + Element root2 = document2.getDocumentElement(); + assertFalse(DomUtilities.isEquivalent(root2, null)); + assertTrue(DomUtilities.isEquivalent(root1, root2)); + + root1.appendChild(document1.createTextNode(" ")); + // Differences in text are NOT significant! + assertTrue(DomUtilities.isEquivalent(root1, root2)); + root2.appendChild(document2.createTextNode(" ")); + assertTrue(DomUtilities.isEquivalent(root1, root2)); + + Element foo1 = document1.createElement("foo"); + Element foo2 = document2.createElement("foo"); + root1.appendChild(foo1); + assertFalse(DomUtilities.isEquivalent(root1, root2)); + root2.appendChild(foo2); + assertTrue(DomUtilities.isEquivalent(root1, root2)); + + root1.appendChild(document1.createElement("bar")); + assertFalse(DomUtilities.isEquivalent(root1, root2)); + root2.appendChild(document2.createElement("bar")); + assertTrue(DomUtilities.isEquivalent(root1, root2)); + + // Add attributes in opposite order + foo1.setAttribute("attribute1", "value1"); + foo1.setAttribute("attribute2", "value2"); + assertFalse(DomUtilities.isEquivalent(root1, root2)); + foo2.setAttribute("attribute2", "value2"); + foo2.setAttribute("attribute1", "valueWrong"); + assertFalse(DomUtilities.isEquivalent(root1, root2)); + foo2.setAttribute("attribute1", "value1"); + assertTrue(DomUtilities.isEquivalent(root1, root2)); + + // TODO - test different tag names + // TODO - test different name spaces! + } + + public void testIsContiguous() throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + document.appendChild(document.createElement("root")); + Element root = document.getDocumentElement(); + root.appendChild(document.createTextNode(" ")); + Element foo = document.createElement("foo"); + root.appendChild(foo); + root.appendChild(document.createTextNode(" ")); + Element bar = document.createElement("bar"); + root.appendChild(bar); + Element baz = document.createElement("baz"); + root.appendChild(baz); + + assertTrue(DomUtilities.isContiguous(Arrays.asList(foo))); + assertTrue(DomUtilities.isContiguous(Arrays.asList(foo, bar))); + assertTrue(DomUtilities.isContiguous(Arrays.asList(foo, bar, baz))); + assertTrue(DomUtilities.isContiguous(Arrays.asList(foo, bar, baz))); + assertTrue(DomUtilities.isContiguous(Arrays.asList(bar, baz, foo))); + assertTrue(DomUtilities.isContiguous(Arrays.asList(baz, bar, foo))); + assertTrue(DomUtilities.isContiguous(Arrays.asList(baz, foo, bar))); + + assertFalse(DomUtilities.isContiguous(Arrays.asList(foo, baz))); + assertFalse(DomUtilities.isContiguous(Arrays.asList(root, baz))); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PointTestCases.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PointTestCases.java index 5e86d69..91d0e13 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PointTestCases.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PointTestCases.java @@ -29,7 +29,7 @@ import junit.framework.TestCase; * Common utilities for the point tests {@link LayoutPointTest} and * {@link ControlPointTest} */ -public abstract class PointTestCases extends TestCase { +public class PointTestCases extends TestCase { LayoutCanvas mCanvas = new TestLayoutCanvas(); protected MouseEvent canvasMouseEvent(int x, int y, int stateMask) { diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java new file mode 100644 index 0000000..b29f9f3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManagerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; + +import org.eclipse.swt.widgets.Shell; + +import java.util.Arrays; + +import junit.framework.TestCase; + +public class SelectionManagerTest extends TestCase { + private SelectionManager createManager() { + LayoutCanvas canvas = new LayoutCanvas(null, null, new Shell(), 0); + return new SelectionManager(canvas); + } + + public void testEmpty() { + SelectionManager manager = createManager(); + + assertNotNull(manager.getSelections()); + assertEquals(0, manager.getSelections().size()); + assertFalse(manager.hasMultiSelection()); + assertTrue(manager.isEmpty()); + } + + public void testBasic() { + SelectionManager manager = createManager(); + assertTrue(manager.isEmpty()); + + UiViewElementNode rootNode = CanvasViewInfoTest.createNode("android.widget.LinearLayout", + true); + ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100); + UiViewElementNode child1Node = CanvasViewInfoTest.createNode(rootNode, + "android.widget.Button", false); + ViewInfo child1 = new ViewInfo("Button1", child1Node, 0, 0, 50, 20); + UiViewElementNode child2Node = CanvasViewInfoTest.createNode(rootNode, + "android.widget.Button", false); + ViewInfo child2 = new ViewInfo("Button2", child2Node, 0, 20, 70, 25); + root.setChildren(Arrays.asList(child1, child2)); + CanvasViewInfo rootView = CanvasViewInfo.create(root, true /* layoutlib5 */).getFirst(); + assertNotNull(rootView); + + manager.selectMultiple(Arrays.asList(rootView, rootView.getChildren().get(0), rootView + .getChildren().get(1))); + assertEquals(3, manager.getSelections().size()); + assertFalse(manager.isEmpty()); + assertTrue(manager.hasMultiSelection()); + + // Expect read-only result; ensure that's the case + try { + manager.getSelections().remove(0); + fail("Result should be read only collection"); + } catch (Exception e) { + ; //ok, what we expected + } + + manager.selectNone(); + assertEquals(0, manager.getSelections().size()); + assertTrue(manager.isEmpty()); + + manager.selectSingle(rootView); + assertEquals(1, manager.getSelections().size()); + assertFalse(manager.isEmpty()); + assertSame(rootView, manager.getSelections().get(0).getViewInfo()); + + manager.selectMultiple(Arrays.asList(rootView, rootView.getChildren().get(0), rootView + .getChildren().get(1))); + assertEquals(3, manager.getSelections().size()); + + manager.deselect(rootView.getChildren().get(0)); + assertEquals(2, manager.getSelections().size()); + manager.deselect(rootView); + assertEquals(1, manager.getSelections().size()); + assertSame(rootView.getChildren().get(1), manager.getSelections().get(0).getViewInfo()); + } + + public void testSelectParent() { + SelectionManager manager = createManager(); + assertTrue(manager.isEmpty()); + + UiViewElementNode rootNode = CanvasViewInfoTest.createNode("android.widget.LinearLayout", + true); + ViewInfo root = new ViewInfo("LinearLayout", rootNode, 10, 10, 100, 100); + UiViewElementNode child1Node = CanvasViewInfoTest.createNode(rootNode, + "android.widget.Button", false); + ViewInfo child1 = new ViewInfo("Button1", child1Node, 0, 0, 50, 20); + UiViewElementNode child2Node = CanvasViewInfoTest.createNode(rootNode, + "android.widget.Button", false); + ViewInfo child2 = new ViewInfo("Button2", child2Node, 0, 20, 70, 25); + root.setChildren(Arrays.asList(child1, child2)); + CanvasViewInfo rootView = CanvasViewInfo.create(root, true /* layoutlib5 */).getFirst(); + assertNotNull(rootView); + + manager.selectMultiple(Arrays.asList(rootView.getChildren().get(0))); + assertEquals(1, manager.getSelections().size()); + assertFalse(manager.isEmpty()); + assertSame(rootView.getChildren().get(0), manager.getSelections().get(0).getViewInfo()); + + manager.selectParent(); + assertEquals(1, manager.getSelections().size()); + assertFalse(manager.isEmpty()); + assertSame(rootView, manager.getSelections().get(0).getViewInfo()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtilsTest.java index 5561e52..de7999c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtilsTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SwtUtilsTest.java @@ -240,18 +240,27 @@ public class SwtUtilsTest extends TestCase { scale, alpha); assertNotNull(result); - ImageData data = result.getImageData(); - byte[] alphaData = data.alphaData; - assertNotNull(alphaData); - assertEquals(20, data.width); - assertEquals(20, data.height); + ImageData outData = result.getImageData(); + assertEquals(20, outData.width); + assertEquals(20, outData.height); + + PaletteData outPalette = outData.palette; + assertNotNull(outPalette); + + byte[] outAlphaData = outData.alphaData; + assertNotNull(outAlphaData); + for (int y = 0; y < 20; y++) { for (int x = 0; x < 20; x++) { int r = y + 60; int g = x + 30; - int expected = r << 16 | g << 8; - assertEquals(expected, data.getPixel(x, y)); - assertEquals(alpha, alphaData[y*20+x]); + + RGB expected = new RGB(r, g, 0); + RGB actual = outPalette.getRGB(outData.getPixel(x, y)); + assertEquals(expected, actual); + + byte actualAlpha = outAlphaData[y*20+x]; + assertEquals(alpha, actualAlpha); } } } @@ -270,30 +279,41 @@ public class SwtUtilsTest extends TestCase { scale, alpha); assertNotNull(result); - ImageData data = result.getImageData(); - byte[] alphaData = data.alphaData; - assertNotNull(alphaData); - assertEquals(120, data.width); - assertEquals(90, data.height); + ImageData outData = result.getImageData(); + assertEquals(120, outData.width); + assertEquals(90, outData.height); + + PaletteData outPalette = outData.palette; + assertNotNull(outPalette); + + byte[] outAlphaData = outData.alphaData; + assertNotNull(outAlphaData); + for (int y = 0; y < 20; y++) { for (int x = 0; x < 20; x++) { int r = y + 10; int g = x + 10; - int expected = r << 16 | g << 8; - assertEquals(expected, data.getPixel(x, y)); - assertEquals(alpha, alphaData[y*120+x]); + + RGB expected = new RGB(r, g, 0); + RGB actual = outPalette.getRGB(outData.getPixel(x, y)); + assertEquals(expected, actual); + + assertEquals(alpha, outAlphaData[y*120+x]); } } for (int y = 70; y < 90; y++) { for (int x = 100; x < 120; x++) { int r = y + 10; int g = x + 10; - int expected = r << 16 | g << 8; - assertEquals(expected, data.getPixel(x, y)); - assertEquals(alpha, alphaData[y*120+x]); + + RGB expected = new RGB(r, g, 0); + RGB actual = outPalette.getRGB(outData.getPixel(x, y)); + assertEquals(expected, actual); + + assertEquals(alpha, outAlphaData[y*120+x]); } } - assertEquals(0, alphaData[40]); + assertEquals(0, outAlphaData[40]); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java index 277089f..9f670cc 100755 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeFactoryTest.java @@ -48,7 +48,7 @@ public class NodeFactoryTest extends TestCase { ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass"); UiViewElementNode uiv = new UiViewElementNode(ved); ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120); - CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst(); + CanvasViewInfo cvi = CanvasViewInfo.create(lvi, true /* layoutlib5 */).getFirst(); // Create a NodeProxy. NodeProxy proxy = m.create(cvi); @@ -95,7 +95,7 @@ public class NodeFactoryTest extends TestCase { ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass"); UiViewElementNode uiv = new UiViewElementNode(ved); ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120); - CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst(); + CanvasViewInfo cvi = CanvasViewInfo.create(lvi, true /* layoutlib5 */).getFirst(); // NodeProxies are cached. Creating the same one twice returns the same proxy. NodeProxy proxy1 = m.create(cvi); @@ -107,7 +107,7 @@ public class NodeFactoryTest extends TestCase { ViewElementDescriptor ved = new ViewElementDescriptor("xml", "com.example.MyJavaClass"); UiViewElementNode uiv = new UiViewElementNode(ved); ViewInfo lvi = new ViewInfo("name", uiv, 10, 12, 110, 120); - CanvasViewInfo cvi = CanvasViewInfo.create(lvi).getFirst(); + CanvasViewInfo cvi = CanvasViewInfo.create(lvi, true /* layoutlib5 */).getFirst(); // NodeProxies are cached. Creating the same one twice returns the same proxy. NodeProxy proxy1 = m.create(cvi); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepositoryTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepositoryTest.java index d18967d..5921e85 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepositoryTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepositoryTest.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre; import com.android.ide.common.api.IViewMetadata.FillPreference; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository.RenderMode; import junit.framework.TestCase; @@ -32,4 +33,31 @@ public class ViewMetadataRepositoryTest extends TestCase { assertEquals(FillPreference.NONE, repository.getFillPreference("foo.bar")); } + + // Ensure that all basenames referenced in the metadata refer to other views in the file + // (e.g. no typos) + public void testRelatedTo() throws Exception { + // Make sure unit tests are run with assertions on + boolean assertionsEnabled = false; + assert assertionsEnabled = true; // Intentional assignment + assertTrue("This unit test must be run with assertions enabled (-ea)", assertionsEnabled); + + ViewMetadataRepository repository = ViewMetadataRepository.get(); + for (String fqcn : repository.getAllFqcns()) { + repository.getRelatedTo(fqcn); + } + } + + public void testSkip() throws Exception { + ViewMetadataRepository repository = ViewMetadataRepository.get(); + assertTrue(repository.getSkip("merge")); + assertFalse(repository.getSkip("android.widget.Button")); + } + + public void testRenderMode() throws Exception { + ViewMetadataRepository repository = ViewMetadataRepository.get(); + assertEquals(RenderMode.NORMAL, repository.getRenderMode("android.widget.Button")); + assertEquals(RenderMode.SKIP, repository.getRenderMode("android.widget.LinearLayout")); + assertEquals(RenderMode.ALONE, repository.getRenderMode("android.widget.TabHost")); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java index 30f709c..ccf4e83 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java @@ -16,15 +16,24 @@ package com.android.ide.eclipse.adt.internal.editors.manifest.model; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; + import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory; import com.android.ide.eclipse.adt.internal.editors.mock.MockXmlNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; import java.util.Iterator; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + import junit.framework.TestCase; public class UiElementNodeTest extends TestCase { @@ -252,4 +261,30 @@ public class UiElementNodeTest extends TestCase { } + public void testlookupNamespacePrefix() throws Exception { + // Setup + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Element rootElement = document.createElement("root"); + Attr attr = document.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, + "xmlns:customPrefix"); + attr.setValue(ANDROID_URI); + rootElement.getAttributes().setNamedItemNS(attr); + document.appendChild(rootElement); + Element root = document.getDocumentElement(); + root.appendChild(document.createTextNode(" ")); + Element foo = document.createElement("foo"); + root.appendChild(foo); + root.appendChild(document.createTextNode(" ")); + Element bar = document.createElement("bar"); + root.appendChild(bar); + Element baz = document.createElement("baz"); + root.appendChild(baz); + + String prefix = UiElementNode.lookupNamespacePrefix(baz, ANDROID_URI); + assertEquals("customPrefix", prefix); + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/ConfigMatchTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/ConfigMatchTest.java index f8dc9fb..f2a6b54 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/ConfigMatchTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/ConfigMatchTest.java @@ -16,14 +16,12 @@ package com.android.ide.eclipse.adt.internal.editors.resources.manager; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; +import com.android.ide.common.resources.ResourceFile; +import com.android.ide.common.resources.ResourceFolder; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.SingleResourceFile; +import com.android.ide.common.resources.configuration.FolderConfiguration; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFile; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; -import com.android.ide.eclipse.adt.internal.resources.manager.SingleResourceFile; import com.android.ide.eclipse.adt.io.IFileWrapper; import com.android.ide.eclipse.adt.io.IFolderWrapper; import com.android.ide.eclipse.mock.Mocks; @@ -33,16 +31,13 @@ import com.android.resources.KeyboardState; import com.android.resources.Navigation; import com.android.resources.NavigationState; import com.android.resources.NightMode; +import com.android.resources.ResourceFolderType; import com.android.resources.ScreenOrientation; import com.android.resources.TouchScreen; -import com.android.sdklib.io.IAbstractFolder; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - import junit.framework.TestCase; public class ConfigMatchTest extends TestCase { @@ -50,8 +45,8 @@ public class ConfigMatchTest extends TestCase { private static final String MISC1_FILENAME = "foo.xml"; //$NON-NLS-1$ private static final String MISC2_FILENAME = "bar.xml"; //$NON-NLS-1$ + private FolderConfiguration mDefaultConfig; private ProjectResources mResources; - private ResourceQualifier[] mQualifierList; private FolderConfiguration config4; private FolderConfiguration config3; private FolderConfiguration config2; @@ -61,15 +56,9 @@ public class ConfigMatchTest extends TestCase { protected void setUp() throws Exception { super.setUp(); - // create a Resource Manager to get a list of qualifier as instantiated by the real code. - // Thanks for QualifierListTest we know this contains all the qualifiers. - ResourceManager manager = ResourceManager.getInstance(); - Field qualifierListField = ResourceManager.class.getDeclaredField("mQualifiers"); - assertNotNull(qualifierListField); - qualifierListField.setAccessible(true); - - // get the actual list. - mQualifierList = (ResourceQualifier[])qualifierListField.get(manager); + // create a default config with all qualifiers. + mDefaultConfig = new FolderConfiguration(); + mDefaultConfig.createDefault(); // create the project resources. mResources = new ProjectResources(null /*project*/); @@ -233,17 +222,18 @@ public class ConfigMatchTest extends TestCase { * this particular qualifier. */ private FolderConfiguration getConfiguration(String... qualifierValues) { - FolderConfiguration config = new FolderConfiguration(); + // FolderConfiguration.getQualifierCount is always valid and up to date. + final int count = FolderConfiguration.getQualifierCount(); - // those must be of the same length - assertEquals(qualifierValues.length, mQualifierList.length); + // Check we have the right number of qualifier. + assertEquals(qualifierValues.length, count); - int index = 0; + FolderConfiguration config = new FolderConfiguration(); - for (ResourceQualifier qualifier : mQualifierList) { - String value = qualifierValues[index++]; + for (int i = 0 ; i < count ; i++) { + String value = qualifierValues[i]; if (value != null) { - assertTrue(qualifier.checkAndSet(value, config)); + assertTrue(mDefaultConfig.getQualifier(i).checkAndSet(value, config)); } } @@ -253,11 +243,11 @@ public class ConfigMatchTest extends TestCase { /** * Adds a folder to the given {@link ProjectResources} with the given * {@link FolderConfiguration}. The folder is filled with files from the provided list. - * @param resources the {@link ProjectResources} in which to add the folder. + * @param resources the {@link ResourceRepository} in which to add the folder. * @param config the {@link FolderConfiguration} for the created folder. * @param memberList the list of files for the folder. */ - private void addFolder(ProjectResources resources, FolderConfiguration config, + private void addFolder(ResourceRepository resources, FolderConfiguration config, IFile[] memberList) throws Exception { // figure out the folder name based on the configuration @@ -267,28 +257,11 @@ public class ConfigMatchTest extends TestCase { IFolder folder = Mocks.createFolder(folderName, memberList); // add it to the resource, and get back a ResourceFolder object. - ResourceFolder resFolder = _addProjectResourceFolder(resources, config, folder); + ResourceFolder resFolder = resources.processFolder(new IFolderWrapper(folder)); // and fill it with files from the list. for (IFile file : memberList) { resFolder.addFile(new SingleResourceFile(new IFileWrapper(file), resFolder)); } } - - /** Calls ProjectResource.add method via reflection to circumvent access - * restrictions that are enforced when running in the plug-in environment - * ie cannot access package or protected members in a different plug-in, even - * if they are in the same declared package as the accessor - */ - private ResourceFolder _addProjectResourceFolder(ProjectResources resources, - FolderConfiguration config, IFolder folder) throws Exception { - - Method addMethod = ProjectResources.class.getDeclaredMethod("add", - ResourceFolderType.class, FolderConfiguration.class, - IAbstractFolder.class); - addMethod.setAccessible(true); - ResourceFolder resFolder = (ResourceFolder)addMethod.invoke(resources, - ResourceFolderType.LAYOUT, config, new IFolderWrapper(folder)); - return resFolder; - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/QualifierListTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/QualifierListTest.java deleted file mode 100644 index 8932a00..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/manager/QualifierListTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2007 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 com.android.ide.eclipse.adt.internal.editors.resources.manager; - -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; - -import java.lang.reflect.Field; - -import junit.framework.TestCase; - -public class QualifierListTest extends TestCase { - - private ResourceManager mManager; - - @Override - public void setUp() throws Exception { - super.setUp(); - - mManager = ResourceManager.getInstance(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - mManager = null; - } - - public void testQualifierList() { - try { - // get the list of qualifier in the resource manager - Field qualifierListField = ResourceManager.class.getDeclaredField("mQualifiers"); - assertNotNull(qualifierListField); - qualifierListField.setAccessible(true); - - // get the actual list. - ResourceQualifier[] qualifierList = - (ResourceQualifier[])qualifierListField.get(mManager); - - // now get the number of qualifier in the FolderConfiguration - Field qualCountField = FolderConfiguration.class.getDeclaredField("INDEX_COUNT"); - assertNotNull(qualCountField); - qualCountField.setAccessible(true); - - // get the constant value - Integer count = (Integer)qualCountField.get(null); - - // now compare - assertEquals(count.intValue(), qualifierList.length); - } catch (SecurityException e) { - assertTrue(false); - } catch (NoSuchFieldException e) { - assertTrue(false); - } catch (IllegalArgumentException e) { - assertTrue(false); - } catch (IllegalAccessException e) { - assertTrue(false); - } - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/xml/HyperlinksTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/xml/HyperlinksTest.java deleted file mode 100644 index ec6f99a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/xml/HyperlinksTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.ide.eclipse.adt.internal.editors.xml; - -import junit.framework.TestCase; - -public class HyperlinksTest extends TestCase { - public void testFqnRegexp() throws Exception { - assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo").matches()); - assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.pk_g.Foo_Bar1"). - matches()); - assertTrue(Hyperlinks.CLASS_PATTERN.matcher("com.android.Foo$Inner").matches()); - - // Should we allow non-standard packages and class names? - // For now, we're allowing it -- see how this works out in practice. - //assertFalse(XmlHyperlinkResolver.CLASS_PATTERN.matcher("Foo.bar").matches()); - assertTrue(Hyperlinks.CLASS_PATTERN.matcher("Foo.bar").matches()); - - assertFalse(Hyperlinks.CLASS_PATTERN.matcher("LinearLayout").matches()); - assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".").matches()); - assertFalse(Hyperlinks.CLASS_PATTERN.matcher(".F").matches()); - assertFalse(Hyperlinks.CLASS_PATTERN.matcher("f.").matches()); - assertFalse(Hyperlinks.CLASS_PATTERN.matcher("Foo").matches()); - assertFalse(Hyperlinks.CLASS_PATTERN.matcher("com.android.1Foo").matches()); - assertFalse(Hyperlinks.CLASS_PATTERN.matcher("1com.Foo").matches()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java new file mode 100644 index 0000000..078e7cb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceHelperTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011 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 com.android.ide.eclipse.adt.internal.resources; + +import static com.android.resources.ResourceType.DIMEN; +import static com.android.resources.ResourceType.LAYOUT; + +import com.android.ide.common.resources.ResourceDeltaKind; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.ResourceQualifier; +import com.android.resources.ResourceType; + +import org.eclipse.core.resources.IResourceDelta; + +import junit.framework.TestCase; + + +/** + * Test ResourceHelper + */ +public class ResourceHelperTest extends TestCase { + + /** + * temp fake qualifier class. + */ + private static class FakeQualifierClass extends ResourceQualifier { + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + return false; + } + + @Override + public boolean equals(Object object) { + return false; + } + + @Override + public String getFolderSegment() { + return null; + } + + @Override + public String getLongDisplayValue() { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public String getShortDisplayValue() { + return null; + } + + @Override + public String getShortName() { + return null; + } + + @Override + public boolean hasFakeValue() { + return false; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean isValid() { + return false; + } + + } + + public void testgetIcon() throws Exception { + // check that the method returns null for an unknown qualifier class + assertNull(ResourceHelper.getIcon(FakeQualifierClass.class)); + + // find all the qualifiers through FolderConfiguration.createdefault() + FolderConfiguration config = new FolderConfiguration(); + config.createDefault(); + final int count = FolderConfiguration.getQualifierCount(); + for (int i = 0 ; i < count ; i++) { + ResourceQualifier qual = config.getQualifier(i); + assertNotNull(qual); + assertNotNull(qual.getClass().getCanonicalName(), + ResourceHelper.getIcon(qual.getClass())); + } + } + + public void testGetResourceDeltaKind() { + assertEquals(ResourceDeltaKind.ADDED, + ResourceHelper.getResourceDeltaKind(IResourceDelta.ADDED)); + assertEquals(ResourceDeltaKind.REMOVED, + ResourceHelper.getResourceDeltaKind(IResourceDelta.REMOVED)); + assertEquals(ResourceDeltaKind.CHANGED, + ResourceHelper.getResourceDeltaKind(IResourceDelta.CHANGED)); + + assertNull(ResourceHelper.getResourceDeltaKind(IResourceDelta.ADDED_PHANTOM)); + } + + public void testParseResource() { + assertNull(ResourceHelper.parseResource("")); + assertNull(ResourceHelper.parseResource("not_a_resource")); + + assertEquals(LAYOUT, ResourceHelper.parseResource("@layout/foo").getFirst()); + assertEquals(DIMEN, ResourceHelper.parseResource("@dimen/foo").getFirst()); + assertEquals(DIMEN, ResourceHelper.parseResource("@android:dimen/foo").getFirst()); + assertEquals("foo", ResourceHelper.parseResource("@layout/foo").getSecond()); + assertEquals("foo", ResourceHelper.parseResource("@dimen/foo").getSecond()); + assertEquals("foo", ResourceHelper.parseResource("@android:dimen/foo").getSecond()); + } + + + public void testIsFileBasedResourceType() throws Exception { + assertTrue(ResourceHelper.isFileBasedResourceType(ResourceType.ANIMATOR)); + assertTrue(ResourceHelper.isFileBasedResourceType(ResourceType.LAYOUT)); + + assertFalse(ResourceHelper.isFileBasedResourceType(ResourceType.STRING)); + assertFalse(ResourceHelper.isFileBasedResourceType(ResourceType.DIMEN)); + assertFalse(ResourceHelper.isFileBasedResourceType(ResourceType.ID)); + + // Both: + assertTrue(ResourceHelper.isFileBasedResourceType(ResourceType.DRAWABLE)); + assertTrue(ResourceHelper.isFileBasedResourceType(ResourceType.COLOR)); + } + + public void testIsValueBasedResourceType() throws Exception { + assertTrue(ResourceHelper.isValueBasedResourceType(ResourceType.STRING)); + assertTrue(ResourceHelper.isValueBasedResourceType(ResourceType.DIMEN)); + assertTrue(ResourceHelper.isValueBasedResourceType(ResourceType.ID)); + + assertFalse(ResourceHelper.isValueBasedResourceType(ResourceType.LAYOUT)); + + // These can be both: + assertTrue(ResourceHelper.isValueBasedResourceType(ResourceType.DRAWABLE)); + assertTrue(ResourceHelper.isValueBasedResourceType(ResourceType.COLOR)); + } + + public void testCanCreateResource() throws Exception { + assertTrue(ResourceHelper.canCreateResource("@layout/foo")); + assertTrue(ResourceHelper.canCreateResource("@string/foo")); + assertTrue(ResourceHelper.canCreateResource("@dimen/foo")); + assertTrue(ResourceHelper.canCreateResource("@color/foo")); + + assertFalse(ResourceHelper.canCreateResource("@typo/foo")); // nonexistent type + assertFalse(ResourceHelper.canCreateResource("@layout/foo bar")); // space + assertFalse(ResourceHelper.canCreateResource("@layout/new")); // keyword + assertFalse(ResourceHelper.canCreateResource("@android:string/foo")); // framework + assertFalse(ResourceHelper.canCreateResource("@android:dimen/foo")); + assertFalse(ResourceHelper.canCreateResource("@android:color/foo")); + } + + public void testStyleToTheme() throws Exception { + assertEquals("Foo", ResourceHelper.styleToTheme("Foo")); + assertEquals("Theme", ResourceHelper.styleToTheme("@android:style/Theme")); + assertEquals("LocalTheme", ResourceHelper.styleToTheme("@style/LocalTheme")); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceNameValidatorTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceNameValidatorTest.java new file mode 100644 index 0000000..b771667 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/resources/ResourceNameValidatorTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 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 com.android.ide.eclipse.adt.internal.resources; + +import com.android.resources.ResourceFolderType; +import com.android.resources.ResourceType; + +import java.util.Collections; + +import junit.framework.TestCase; + +public class ResourceNameValidatorTest extends TestCase { + public void testValidator() throws Exception { + // Valid + ResourceNameValidator validator = ResourceNameValidator.create(true, + ResourceFolderType.VALUES); + assertTrue(validator.isValid("foo") == null); + assertTrue(validator.isValid("foo.xml") == null); + assertTrue(validator.isValid("Foo123_$") == null); + + // Invalid + assertTrue(validator.isValid("") != null); + assertTrue(validator.isValid(" ") != null); + assertTrue(validator.isValid("foo.xm") != null); + assertTrue(validator.isValid("foo bar") != null); + assertTrue(validator.isValid("1foo") != null); + assertTrue(validator.isValid("foo%bar") != null); + assertTrue(ResourceNameValidator.create(true, Collections.singleton("foo"), + ResourceType.STRING).isValid("foo") != null); + + // Only lowercase chars allowed in file-based resource names + assertTrue(ResourceNameValidator.create(true, ResourceFolderType.LAYOUT) + .isValid("Foo123_$") != null); + assertTrue(ResourceNameValidator.create(true, ResourceFolderType.LAYOUT) + .isValid("foo123_") == null); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ResourceNameValidatorTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ResourceNameValidatorTest.java deleted file mode 100644 index 5ee6793..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ResourceNameValidatorTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2010 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 com.android.ide.eclipse.adt.internal.wizards.newxmlfile; - -import java.util.Collections; - -import junit.framework.TestCase; - -public class ResourceNameValidatorTest extends TestCase { - public void testValidator() throws Exception { - // Valid - assertTrue(ResourceNameValidator.create(true).isValid("foo") == null); - assertTrue(ResourceNameValidator.create(true).isValid("foo.xml") == null); - assertTrue(ResourceNameValidator.create(true).isValid("Foo123_$") == null); - - // Invalid - assertTrue(ResourceNameValidator.create(true).isValid("") != null); - assertTrue(ResourceNameValidator.create(true).isValid(" ") != null); - assertTrue(ResourceNameValidator.create(true).isValid("foo.xm") != null); - assertTrue(ResourceNameValidator.create(true).isValid("foo bar") != null); - assertTrue(ResourceNameValidator.create(true).isValid("1foo") != null); - assertTrue(ResourceNameValidator.create(true).isValid("foo%bar") != null); - assertTrue(ResourceNameValidator.create(true, Collections.singleton("foo")) - .isValid("foo") != null); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.traceview/.settings/org.eclipse.jdt.core.prefs b/eclipse/plugins/com.android.ide.eclipse.traceview/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.traceview/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/eclipse/plugins/com.android.ide.eclipse.traceview/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.traceview/META-INF/MANIFEST.MF index ab9a5c2..35c62e5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.traceview/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.traceview/META-INF/MANIFEST.MF @@ -2,12 +2,12 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Traceview Bundle-SymbolicName: com.android.ide.eclipse.traceview;singleton:=true -Bundle-Version: 10.0.0.qualifier +Bundle-Version: 11.0.0.qualifier Bundle-Activator: com.android.ide.eclipse.traceview.TraceviewPlugin Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.ui.ide, - com.android.ide.eclipse.ddms;bundle-version="10.0.0", + com.android.ide.eclipse.ddms;bundle-version="11.0.0", org.eclipse.core.filesystem, org.eclipse.core.resources, org.eclipse.jdt.core, diff --git a/eclipse/plugins/com.android.ide.eclipse.traceview/about.ini b/eclipse/plugins/com.android.ide.eclipse.traceview/about.ini new file mode 100644 index 0000000..b61f646 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.traceview/about.ini @@ -0,0 +1,2 @@ +aboutText=%blurb +featureImage=icons/traceview-32.png
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.traceview/about.properties b/eclipse/plugins/com.android.ide.eclipse.traceview/about.properties new file mode 100755 index 0000000..b33e9f3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.traceview/about.properties @@ -0,0 +1,7 @@ +blurb=Traceview\n\
+\n\
+Version\: {featureVersion}\n\
+\n\
+(c) Copyright 2011 The Android Open Source Project. All rights reserved.\n\
+Visit http://developer.android.com/sdk/eclipse-adt.html
+
diff --git a/eclipse/plugins/com.android.ide.eclipse.traceview/build.properties b/eclipse/plugins/com.android.ide.eclipse.traceview/build.properties index 049e0d1..f529710 100644 --- a/eclipse/plugins/com.android.ide.eclipse.traceview/build.properties +++ b/eclipse/plugins/com.android.ide.eclipse.traceview/build.properties @@ -4,4 +4,6 @@ bin.includes = META-INF/,\ .,\ plugin.xml,\ libs/traceview.jar,\ - icons/ + icons/,\ + about.ini,\ + about.properties diff --git a/eclipse/plugins/com.android.ide.eclipse.traceview/icons/traceview-32.png b/eclipse/plugins/com.android.ide.eclipse.traceview/icons/traceview-32.png Binary files differnew file mode 100644 index 0000000..4916737 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.traceview/icons/traceview-32.png diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh index dec4df7..f2ce44c 100755 --- a/eclipse/scripts/create_all_symlinks.sh +++ b/eclipse/scripts/create_all_symlinks.sh @@ -27,5 +27,7 @@ echo ; echo "### HIERARCHYVIEWER ###" ; echo $DEST/create_hierarchyviewer_symlinks.sh "$*" echo ; echo "### TRACEVIEW ###" ; echo $DEST/create_traceview_symlinks.sh "$*" +echo ; echo "### SDKMANAGER ###" ; echo +$DEST/create_sdkman_symlinks.sh "$*" echo "### $0 done" diff --git a/eclipse/scripts/create_ddms_symlinks.sh b/eclipse/scripts/create_ddms_symlinks.sh index 8791316..48f5c51 100755 --- a/eclipse/scripts/create_ddms_symlinks.sh +++ b/eclipse/scripts/create_ddms_symlinks.sh @@ -63,11 +63,12 @@ for i in prebuilt/common/jfreechart/*.jar; do cpfile $DEST $i done -LIBS="ddmlib ddmuilib" +COPY_LIBS="ddmlib ddmuilib" +ALL_LIBS="$COPY_LIBS swtmenubar" echo "make java libs ..." -make -j3 showcommands $LIBS || die "DDMS: Fail to build one of $LIBS." +make -j3 showcommands $ALL_LIBS || die "DDMS: Fail to build one of $ALL_LIBS." -for LIB in $LIBS; do +for LIB in $COPY_LIBS; do cpfile $DEST out/host/$PLATFORM/framework/$LIB.jar done diff --git a/eclipse/scripts/create_hierarchyviewer_symlinks.sh b/eclipse/scripts/create_hierarchyviewer_symlinks.sh index e0439ef..47dbe7f 100755 --- a/eclipse/scripts/create_hierarchyviewer_symlinks.sh +++ b/eclipse/scripts/create_hierarchyviewer_symlinks.sh @@ -60,10 +60,11 @@ DEST=$BASE/libs mkdir -p $DEST -LIBS="hierarchyviewerlib " +COPY_LIBS="hierarchyviewerlib" +ALL_LIBS="$COPY_LIBS swtmenubar" echo "make java libs ..." -make -j3 showcommands $LIBS || die "Hierarchy Viewer: Fail to build one of $LIBS." +make -j3 showcommands $ALL_LIBS || die "Hierarchy Viewer: Fail to build one of $ALL_LIBS." -for LIB in $LIBS; do +for LIB in $COPY_LIBS; do cpfile $DEST out/host/$PLATFORM/framework/$LIB.jar done diff --git a/eclipse/scripts/create_sdkman_symlinks.sh b/eclipse/scripts/create_sdkman_symlinks.sh new file mode 100755 index 0000000..d965195 --- /dev/null +++ b/eclipse/scripts/create_sdkman_symlinks.sh @@ -0,0 +1,16 @@ +#!/bin/bash +function die() { + echo "Error: $*" + exit 1 +} + +set -e # fail early + +# CD to the top android directory +D=`dirname "$0"` +cd "$D/../../../" + +LIBS="swtmenubar" + +echo "SDK Manager: make java libs $LIBS" +make -j3 showcommands $LIBS || die "SDK Manager: Failed to build one of $LIBS." diff --git a/eclipse/scripts/update_version.sh b/eclipse/scripts/update_version.sh index a288965..c7198af 100644..100755 --- a/eclipse/scripts/update_version.sh +++ b/eclipse/scripts/update_version.sh @@ -22,16 +22,10 @@ if [ `basename "$PWD"` != "eclipse" ]; then fi # quote dots for regexps -OLD="${OLD//./\.}" -NEW="${NEW//./\.}" - -# Find all the files with the old pattern, except changes.txt and -# p4 edit them. Skip that if there's no p4 in path. -if which g4 1>/dev/null 2>/dev/null ; then - grep -rl "$OLD" * | grep -E "\.xml$|\.MF$" | xargs -n 5 g4 edit -fi +OLD="${OLD//./\.}\.qualifier" +NEW="${NEW//./\.}\.qualifier" # Now find the same files but this time use sed to replace in-place with # the new pattern. Old files get backuped with the .old extension. -grep -rl "$OLD" * | grep -E "\.xml$|\.MF$" | xargs -n 1 sed -i.old "s/$OLD/$NEW/g" +grep -rl "$OLD" * | grep -E "\.xml$|\.MF$" | xargs -n 1 sed -i -e "s/$OLD/$NEW/g" diff --git a/eclipse/sites/external/site.xml b/eclipse/sites/external/site.xml index f4810c8..bd3a044 100644 --- a/eclipse/sites/external/site.xml +++ b/eclipse/sites/external/site.xml @@ -3,16 +3,16 @@ <description url="https://dl-ssl.google.com/android/eclipse/"> Update Site for Android Development Toolkit </description> - <feature url="features/com.android.ide.eclipse.adt_10.0.0.qualifier.jar" id="com.android.ide.eclipse.adt" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.adt_11.0.0.qualifier.jar" id="com.android.ide.eclipse.adt" version="11.0.0.qualifier"> <category name="developer"/> </feature> - <feature url="features/com.android.ide.eclipse.ddms_10.0.0.qualifier.jar" id="com.android.ide.eclipse.ddms" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.ddms_11.0.0.qualifier.jar" id="com.android.ide.eclipse.ddms" version="11.0.0.qualifier"> <category name="developer"/> </feature> - <feature url="features/com.android.ide.eclipse.hierarchyviewer_10.0.0.qualifier.jar" id="com.android.ide.eclipse.hierarchyviewer" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.hierarchyviewer_11.0.0.qualifier.jar" id="com.android.ide.eclipse.hierarchyviewer" version="11.0.0.qualifier"> <category name="developer"/> </feature> - <feature url="features/com.android.ide.eclipse.traceview_10.0.0.qualifier.jar" id="com.android.ide.eclipse.traceview" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.traceview_11.0.0.qualifier.jar" id="com.android.ide.eclipse.traceview" version="11.0.0.qualifier"> <category name="developer"/> </feature> <category-def name="developer" label="Developer Tools"> diff --git a/eclipse/sites/internal/.gitignore b/eclipse/sites/internal/.gitignore new file mode 100644 index 0000000..ccfee1d --- /dev/null +++ b/eclipse/sites/internal/.gitignore @@ -0,0 +1,2 @@ +*.jar +*/*.jar
\ No newline at end of file diff --git a/eclipse/sites/internal/site.xml b/eclipse/sites/internal/site.xml index 40abd3b..02ba7fe 100644 --- a/eclipse/sites/internal/site.xml +++ b/eclipse/sites/internal/site.xml @@ -3,24 +3,24 @@ <description url="https://android.corp.google.com/adt/"> Update Site for Android Development Toolkit </description> - <feature url="features/com.android.ide.eclipse.adt_10.0.0.qualifier.jar" id="com.android.ide.eclipse.adt" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.adt_11.0.0.qualifier.jar" id="com.android.ide.eclipse.adt" version="11.0.0.qualifier"> <category name="developer"/> </feature> - <feature url="features/com.android.ide.eclipse.ddms_10.0.0.qualifier.jar" id="com.android.ide.eclipse.ddms" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.ddms_11.0.0.qualifier.jar" id="com.android.ide.eclipse.ddms" version="11.0.0.qualifier"> <category name="developer"/> <category name="platform"/> </feature> - <feature url="features/com.android.ide.eclipse.hierarchyviewer_10.0.0.qualifier.jar" id="com.android.ide.eclipse.hierarchyviewer" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.hierarchyviewer_11.0.0.qualifier.jar" id="com.android.ide.eclipse.hierarchyviewer" version="11.0.0.qualifier"> <category name="developer"/> <category name="platform"/> </feature> - <feature url="features/com.android.ide.eclipse.tests_10.0.0.qualifier.jar" id="com.android.ide.eclipse.tests" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.tests_11.0.0.qualifier.jar" id="com.android.ide.eclipse.tests" version="11.0.0.qualifier"> <category name="test"/> </feature> - <feature url="features/com.android.ide.eclipse.pdt_10.0.0.qualifier.jar" id="com.android.ide.eclipse.pdt" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.pdt_11.0.0.qualifier.jar" id="com.android.ide.eclipse.pdt" version="11.0.0.qualifier"> <category name="platform"/> </feature> - <feature url="features/com.android.ide.eclipse.traceview_10.0.0.qualifier.jar" id="com.android.ide.eclipse.traceview" version="10.0.0.qualifier"> + <feature url="features/com.android.ide.eclipse.traceview_11.0.0.qualifier.jar" id="com.android.ide.eclipse.traceview" version="11.0.0.qualifier"> <category name="developer"/> <category name="platform"/> </feature> diff --git a/emulator/mksdcard/mksdcard.c b/emulator/mksdcard/mksdcard.c index c85d0f8..c9a3eeb 100644 --- a/emulator/mksdcard/mksdcard.c +++ b/emulator/mksdcard/mksdcard.c @@ -27,7 +27,7 @@ /* a simple and portable program used to generate a blank FAT32 image file * - * usage: mksdcard [-l label] <size> <filename> + * usage: mksdcard [-l label] <size> <filename> */ #include <time.h> @@ -43,6 +43,10 @@ #define BACKUP_BOOT_SECTOR 6 #define NUM_FATS 2 +/* sectors_per_disk is encoded as a signed int */ +#define MAX_SECTORS_PER_DISK 0x7FFFFFFF +#define MAX_DISK_SIZE ((Wide)MAX_SECTORS_PER_DISK * BYTES_PER_SECTOR) + typedef long long Wide; /* might be something else if you don't use GCC */ typedef unsigned char Byte; typedef Byte* Bytes; @@ -53,10 +57,10 @@ typedef Byte* Bytes; #define POKES(p,v) ( BYTE_(p,0) = (Byte)(v), BYTE_(p,1) = (Byte)((v) >> 8) ) #define POKEW(p,v) ( BYTE_(p,0) = (Byte)(v), BYTE_(p,1) = (Byte)((v) >> 8), BYTE_(p,2) = (Byte)((v) >> 16), BYTE_(p,3) = (Byte)((v) >> 24) ) -static Byte s_boot_sector [ BYTES_PER_SECTOR ]; /* boot sector */ +static Byte s_boot_sector [ BYTES_PER_SECTOR ]; /* boot sector */ static Byte s_fsinfo_sector [ BYTES_PER_SECTOR ]; /* FS Info sector */ -static Byte s_fat_head [ BYTES_PER_SECTOR ]; /* first FAT sector */ -static Byte s_zero_sector [ BYTES_PER_SECTOR ]; /* empty sector */ +static Byte s_fat_head [ BYTES_PER_SECTOR ]; /* first FAT sector */ +static Byte s_zero_sector [ BYTES_PER_SECTOR ]; /* empty sector */ /* this is the date and time when creating the disk */ static int @@ -172,22 +176,28 @@ fat_init( Bytes fat ) static int write_sector( FILE* file, Bytes sector ) { - return fwrite( sector, 1, 512, file ) != 512; + int result = fwrite( sector, 1, BYTES_PER_SECTOR, file ) != BYTES_PER_SECTOR; + if (result) { + fprintf(stderr, "Failed to write sector of %d bytes: %s\n", BYTES_PER_SECTOR, strerror(errno)); + } + return result; } static int write_empty( FILE* file, Wide count ) { - static Byte empty[64*1024]; + static Byte empty[256*1024]; + memset(empty, 0, sizeof(empty)); - count *= 512; + count *= BYTES_PER_SECTOR; while (count > 0) { int len = sizeof(empty); if (len > count) - len = count; - - if ( fwrite( empty, 1, len, file ) != (size_t)len ) + len = count; + if ( fwrite( empty, 1, len, file ) != (size_t)len ) { + fprintf(stderr, "Failed to write %d bytes: %s\n", len, strerror(errno)); return 1; + } count -= len; } @@ -201,6 +211,10 @@ static void usage (void) fprintf(stderr, " if <size> is a simple integer, it specifies a size in bytes\n" ); fprintf(stderr, " if <size> is an integer followed by 'K', it specifies a size in KiB\n" ); fprintf(stderr, " if <size> is an integer followed by 'M', it specifies a size in MiB\n" ); + fprintf(stderr, " if <size> is an integer followed by 'G', it specifies a size in GiB\n" ); + fprintf(stderr, "\nMinimum size is 9M. The Android emulator cannot use smaller images.\n" ); + fprintf(stderr, "Maximum size is %lld bytes, %lldK, %lldM or %lldG\n", + MAX_DISK_SIZE, MAX_DISK_SIZE >> 10, MAX_DISK_SIZE >> 20, MAX_DISK_SIZE >> 30); exit(1); } @@ -211,7 +225,7 @@ int main( int argc, char** argv ) int sectors_per_disk; char* end; const char* label = NULL; - FILE* f; + FILE* f = NULL; for ( ; argc > 1 && argv[1][0] == '-'; argc--, argv++ ) { @@ -238,19 +252,28 @@ int main( int argc, char** argv ) if (argc != 3) usage(); - disk_size = strtol( argv[1], &end, 10 ); - if (disk_size == 0 && errno == EINVAL) + disk_size = strtoll( argv[1], &end, 10 ); + if (disk_size <= 0 || errno == EINVAL || errno == ERANGE) { + fprintf(stderr, "Invalid argument size '%s'\n\n", argv[1]); usage(); + } if (*end == 'K') disk_size *= 1024; else if (*end == 'M') disk_size *= 1024*1024; + else if (*end == 'G') + disk_size *= 1024*1024*1024; - if (disk_size < 8*1024*1024) - fprintf(stderr, "### WARNING : SD Card images < 8 MB cannot be used with the Android emulator\n"); + if (disk_size < 9*1024*1024) { + fprintf(stderr, "Invalid argument: size '%s' is too small.\n\n", argv[1]); + usage(); + } else if (disk_size > MAX_DISK_SIZE) { + fprintf(stderr, "Invalid argument: size '%s' is too large.\n\n", argv[1]); + usage(); + } - sectors_per_disk = disk_size / 512; + sectors_per_disk = disk_size / BYTES_PER_SECTOR; sectors_per_fat = get_sectors_per_fat( disk_size, get_sectors_per_cluster( disk_size ) ); boot_sector_init( s_boot_sector, s_fsinfo_sector, disk_size, NULL ); @@ -258,7 +281,8 @@ int main( int argc, char** argv ) f = fopen( argv[2], "wb" ); if ( !f ) { - fprintf(stderr, "could not create file '%s', aborting...\n", argv[2] ); + fprintf(stderr, "Could not create file '%s': %s\n", argv[2], strerror(errno)); + goto FailWrite; } /* here's the layout: @@ -274,7 +298,7 @@ int main( int argc, char** argv ) * zero sectors */ - if ( write_sector( f, s_boot_sector ) ) goto FailWrite; + if ( write_sector( f, s_boot_sector ) ) goto FailWrite; if ( write_sector( f, s_fsinfo_sector ) ) goto FailWrite; if ( BACKUP_BOOT_SECTOR > 0 ) { if ( write_empty( f, BACKUP_BOOT_SECTOR - 2 ) ) goto FailWrite; @@ -282,8 +306,7 @@ int main( int argc, char** argv ) if ( write_sector( f, s_fsinfo_sector ) ) goto FailWrite; if ( write_empty( f, RESERVED_SECTORS - 2 - BACKUP_BOOT_SECTOR ) ) goto FailWrite; } - else - if ( write_empty( f, RESERVED_SECTORS - 2 ) ) goto FailWrite; + else if ( write_empty( f, RESERVED_SECTORS - 2 ) ) goto FailWrite; if ( write_sector( f, s_fat_head ) ) goto FailWrite; if ( write_empty( f, sectors_per_fat-1 ) ) goto FailWrite; @@ -297,8 +320,10 @@ int main( int argc, char** argv ) return 0; FailWrite: - fprintf(stderr, "could not write to '%s', aborting...\n", argv[2] ); - unlink( argv[2] ); - fclose(f); + if (f != NULL) { + fclose(f); + unlink( argv[2] ); + fprintf(stderr, "File '%s' was not created.\n", argv[2]); + } return 1; } diff --git a/emulator/qemud/qemud.c b/emulator/qemud/qemud.c index e1c7b54..dc04de8 100644 --- a/emulator/qemud/qemud.c +++ b/emulator/qemud/qemud.c @@ -81,7 +81,7 @@ /* name of the single control socket used by the daemon */ #define CONTROL_SOCKET_NAME "qemud" -#define DEBUG 1 +#define DEBUG 0 #define T_ACTIVE 0 /* set to 1 to dump traffic */ #if DEBUG diff --git a/emulator/tests/Android.mk b/emulator/tests/Android.mk new file mode 100644 index 0000000..04917f4 --- /dev/null +++ b/emulator/tests/Android.mk @@ -0,0 +1,17 @@ +# This directory contains various host tests to be used with the emulator +# NOTE: Most of these are only built and run on Linux. + +LOCAL_PATH := $(call my-dir) + +# The test-qemud-pipes program is used to check the execution of QEMUD Pipes +# See external/qemu/docs/ANDROID-QEMUD-PIPES.TXT for details. +# +ifeq ($(HOST_OS),XXXXlinux) + +include $(CLEAR_VARS) +LOCAL_MODULE := test-qemud-pipes +LOCAL_SRC_FILES := test-qemud-pipes.c +LOCAL_MODULE_TAGS := debug +include $(BUILD_HOST_EXECUTABLE) + +endif # HOST_OS == linux
\ No newline at end of file diff --git a/emulator/tests/test-qemud-pipes.c b/emulator/tests/test-qemud-pipes.c new file mode 100644 index 0000000..f5db531 --- /dev/null +++ b/emulator/tests/test-qemud-pipes.c @@ -0,0 +1,113 @@ +/* This program is used to test the QEMUD fast pipes. + * See external/qemu/docs/ANDROID-QEMUD-PIPES.TXT for details. + * + * The program acts as a simple TCP server that accepts data and sends + * them back to the client. + */ + +#include <sys/socket.h> +#include <net/inet.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#define DEFAULT_PORT 8012 + +static void +socket_close(int sock) +{ + int old_errno = errno; + close(sock); + errno = old_errno; +} + +static int +socket_loopback_server( int port, int type ) +{ + struct sockaddr_in addr; + + int sock = socket(AF_INET, type, 0); + if (sock < 0) { + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + int n = 1; + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); + + if (TEMP_FAILURE_RETRY(bind(sock, &addr, sizeof(addr))) < 0) { + socket_close(sock); + return -1; + } + + if (type == SOCK_STREAM) { + if (TEMP_FAILURE_RETRY(listen(sock, 4)) < 0) { + socket_close(sock); + return -1; + } + } + + return sock; +} + +int main(void) +{ + int sock, client; + int port = DEFAULT_PORT; + + printf("Starting pipe test server on local port %d\n", port); + sock = socket_loopback_server( port, SOCK_STREAM ); + if (sock < 0) { + fprintf(stderr, "Could not start server: %s\n", strerror(errno)); + return 1; + } + + client = accept(sock, NULL, NULL); + if (client < 0) { + fprintf(stderr, "Server error: %s\n", strerror(errno)); + return 2; + } + printf("Client connected!\n"); + + /* Now, accept any incoming data, and send it back */ + for (;;) { + char buff[1024], *p; + int ret, count; + + do { + ret = read(client, buff, sizeof(buff)); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + fprintf(stderr, "Client read error: %s\n", strerror(errno)); + close(client); + return 3; + } + count = ret; + p = buff; + printf(" received: %d bytes\n", count); + + while (count > 0) { + do { + ret = write(client, p, count); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) { + fprintf(stderr, "Client write error: %s\n", strerror(errno)); + close(client); + return 4; + } + printf(" sent: %d bytes\n", ret); + + p += ret; + count -= ret; + } + } + + return 0; +} diff --git a/eventanalyzer/NOTICE b/eventanalyzer/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/eventanalyzer/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/files/devices.xml b/files/devices.xml index 09171c9..ca26416 100644 --- a/files/devices.xml +++ b/files/devices.xml @@ -234,6 +234,33 @@ </d:config> </d:device> + <d:device name="4in WVGA (Nexus S)"> + <d:default> + <d:screen-size>normal</d:screen-size> + <d:screen-ratio>long</d:screen-ratio> + <d:screen-orientation>port</d:screen-orientation> + <d:pixel-density>hdpi</d:pixel-density> + <d:touch-type>finger</d:touch-type> + <d:keyboard-state>keyssoft</d:keyboard-state> + <d:text-input-method>nokeys</d:text-input-method> + <d:nav-state>navexposed</d:nav-state> + <d:nav-method>nonav</d:nav-method> + <d:screen-dimension> + <d:size>480</d:size> + <d:size>800</d:size> + </d:screen-dimension> + <d:xdpi>235</d:xdpi> + <d:ydpi>235</d:ydpi> + </d:default> + + <d:config name="Portrait"> + <d:screen-orientation>port</d:screen-orientation> + </d:config> + <d:config name="Landscape"> + <d:screen-orientation>land</d:screen-orientation> + </d:config> + </d:device> + <d:device name="5.1in WVGA"> <d:default> <d:screen-size>large</d:screen-size> @@ -288,7 +315,7 @@ </d:config> </d:device> - <d:device name="10.1in WXGA"> + <d:device name="10.1in WXGA (Tablet)"> <d:default> <d:screen-size>xlarge</d:screen-size> <d:screen-ratio>long</d:screen-ratio> diff --git a/files/tools_source.properties b/files/tools_source.properties index 68ec0ae..ca992d1 100644 --- a/files/tools_source.properties +++ b/files/tools_source.properties @@ -1,3 +1,3 @@ Pkg.UserSrc=false -Pkg.Revision=10 +Pkg.Revision=11 Platform.MinPlatformToolsRev=3 diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java b/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java index df2a63e..ba346df 100644 --- a/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java +++ b/hierarchyviewer/src/com/android/hierarchyviewer/scene/ViewManager.java @@ -35,6 +35,10 @@ public class ViewManager { sendCommand("REQUEST_LAYOUT", device, window, params); } + public static void outputDisplayList(IDevice device, Window window, String params) { + sendCommand("OUTPUT_DISPLAYLIST", device, window, params); + } + private static void sendCommand(String command, IDevice device, Window window, String params) { Socket socket = null; BufferedWriter out = null; diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java index a7db985..82375e0 100644 --- a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java @@ -29,6 +29,7 @@ import com.android.hierarchyviewer.scene.ViewManager; import com.android.hierarchyviewer.scene.ViewNode; import com.android.hierarchyviewer.scene.WindowsLoader; import com.android.hierarchyviewer.scene.ProfilesLoader; +import com.android.hierarchyviewer.ui.action.DumpDisplayListAction; import com.android.hierarchyviewer.ui.util.PsdFileFilter; import com.android.hierarchyviewer.util.OS; import com.android.hierarchyviewer.util.WorkerThread; @@ -145,6 +146,7 @@ public class Workspace extends JFrame { private JPanel mainPanel; private JProgressBar progress; private JToolBar buttonsPanel; + private JToolBar commandButtonsPanel; private JComponent deviceSelector; private DevicesTableModel devicesTableModel; @@ -154,6 +156,7 @@ public class Workspace extends JFrame { private Window currentWindow = Window.FOCUSED_WINDOW; private JButton displayNodeButton; + private JButton dumpDisplayListButton; private JButton captureLayersButton; private JButton invalidateButton; private JButton requestLayoutButton; @@ -202,6 +205,7 @@ public class Workspace extends JFrame { actionsMap.put(StopServerAction.ACTION_NAME, new StopServerAction(this)); actionsMap.put(InvalidateAction.ACTION_NAME, new InvalidateAction(this)); actionsMap.put(RequestLayoutAction.ACTION_NAME, new RequestLayoutAction(this)); + actionsMap.put(DumpDisplayListAction.ACTION_NAME, new DumpDisplayListAction(this)); actionsMap.put(CaptureNodeAction.ACTION_NAME, new CaptureNodeAction(this)); actionsMap.put(CaptureLayersAction.ACTION_NAME, new CaptureLayersAction(this)); actionsMap.put(RefreshWindowsAction.ACTION_NAME, new RefreshWindowsAction(this)); @@ -210,11 +214,12 @@ public class Workspace extends JFrame { private JComponent buildMainPanel() { mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); - mainPanel.add(buildToolBar(), BorderLayout.PAGE_START); + commandButtonsPanel = buildToolBar(); + mainPanel.add(commandButtonsPanel, BorderLayout.PAGE_START); mainPanel.add(deviceSelector = buildDeviceSelector(), BorderLayout.CENTER); mainPanel.add(buildStatusPanel(), BorderLayout.SOUTH); - mainPanel.setPreferredSize(new Dimension(950, 800)); + mainPanel.setPreferredSize(new Dimension(1200, 800)); return mainPanel; } @@ -481,6 +486,11 @@ public class Workspace extends JFrame { displayNodeButton.putClientProperty("JButton.segmentPosition", "first"); toolBar.add(displayNodeButton); + dumpDisplayListButton = new JButton(); + dumpDisplayListButton.setAction(actionsMap.get(DumpDisplayListAction.ACTION_NAME)); + dumpDisplayListButton.putClientProperty("JButton.buttonType", "segmentedTextured"); + dumpDisplayListButton.putClientProperty("JButton.segmentPosition", "middle"); + captureLayersButton = new JButton(); captureLayersButton.setAction(actionsMap.get(CaptureLayersAction.ACTION_NAME)); captureLayersButton.putClientProperty("JButton.buttonType", "segmentedTextured"); @@ -502,6 +512,17 @@ public class Workspace extends JFrame { return toolBar; } + private void setupProtocolDependentToolbar() { + // Some functionality is only enabled in certain versions of the protocol. + // Add/remove those buttons here + if (protocolVersion < 4) { + commandButtonsPanel.remove(dumpDisplayListButton); + } else if (dumpDisplayListButton.getParent() == null) { + commandButtonsPanel.add(dumpDisplayListButton, + commandButtonsPanel.getComponentCount() - 1); + } + } + private JMenuBar buildMenuBar() { JMenuBar menuBar = new JMenuBar(); @@ -885,6 +906,7 @@ public class Workspace extends JFrame { displayNodeButton.setEnabled(false); captureLayersButton.setEnabled(false); invalidateButton.setEnabled(false); + dumpDisplayListButton.setEnabled(false); requestLayoutButton.setEnabled(false); graphViewButton.setEnabled(false); pixelPerfectViewButton.setEnabled(false); @@ -914,6 +936,7 @@ public class Workspace extends JFrame { displayNodeButton.setEnabled(false); captureLayersButton.setEnabled(false); invalidateButton.setEnabled(false); + dumpDisplayListButton.setEnabled(false); graphViewButton.setEnabled(false); pixelPerfectViewButton.setEnabled(false); requestLayoutButton.setEnabled(false); @@ -1008,6 +1031,13 @@ public class Workspace extends JFrame { return new CaptureNodeTask(); } + public SwingWorker<?, ?> outputDisplayList() { + if (scene.getFocusedObject() == null) { + return null; + } + return new DumpDisplayListTask(); + } + public SwingWorker<?, ?> captureLayers() { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter(new PsdFileFilter()); @@ -1081,6 +1111,27 @@ public class Workspace extends JFrame { } } + private class DumpDisplayListTask extends SwingWorker<Object, Void> { + private String captureParams; + + private DumpDisplayListTask() { + captureParams = scene.getFocusedObject().toString(); + beginTask(); + } + + @Override + @WorkerThread + protected Object doInBackground() throws Exception { + ViewManager.outputDisplayList(currentDevice, currentWindow, captureParams); + return null; + } + + @Override + protected void done() { + endTask(); + } + } + private class RequestLayoutTask extends SwingWorker<Object, Void> { private String captureParams; @@ -1182,7 +1233,7 @@ public class Workspace extends JFrame { WindowsResult result = get(); protocolVersion = result.protocolVersion; serverVersion = result.serverVersion; - + setupProtocolDependentToolbar(); windowsTableModel.clear(); windowsTableModel.addWindows(result.windows); } catch (ExecutionException e) { @@ -1324,6 +1375,7 @@ public class Workspace extends JFrame { public void focusChanged(ObjectSceneEvent e, Object oldFocus, Object newFocus) { displayNodeButton.setEnabled(true); invalidateButton.setEnabled(true); + dumpDisplayListButton.setEnabled(true); requestLayoutButton.setEnabled(true); Set<Object> selection = new HashSet<Object>(); diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/action/DumpDisplayListAction.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/action/DumpDisplayListAction.java new file mode 100644 index 0000000..3e66794 --- /dev/null +++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/action/DumpDisplayListAction.java @@ -0,0 +1,39 @@ +/* + * 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.hierarchyviewer.ui.action; + +import com.android.hierarchyviewer.ui.Workspace; + +import javax.swing.KeyStroke; +import java.awt.event.KeyEvent; +import java.awt.event.ActionEvent; +import java.awt.Toolkit; + +public class DumpDisplayListAction extends BackgroundAction { + public static final String ACTION_NAME = "dumpDisplayList"; + private Workspace mWorkspace; + + public DumpDisplayListAction(Workspace workspace) { + putValue(NAME, "Dump DisplayList"); + putValue(SHORT_DESCRIPTION, "Dump DisplayList"); + putValue(LONG_DESCRIPTION, "Dump DisplayList"); + this.mWorkspace = workspace; + } + + public void actionPerformed(ActionEvent e) { + executeBackgroundTask(mWorkspace.outputDisplayList()); + } +} diff --git a/hierarchyviewer2/app/.classpath b/hierarchyviewer2/app/.classpath index d75889a..c5a657c 100644 --- a/hierarchyviewer2/app/.classpath +++ b/hierarchyviewer2/app/.classpath @@ -7,5 +7,6 @@ <classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/> <classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/> <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> + <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/swtmenubar.jar" sourcepath="/ANDROID_SRC/sdk/swtmenubar/src"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/hierarchyviewer2/app/Android.mk b/hierarchyviewer2/app/Android.mk index 2927f1d..0e00273 100644 --- a/hierarchyviewer2/app/Android.mk +++ b/hierarchyviewer2/app/Android.mk @@ -12,6 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -HIERARCHYVIEWERAPP_LOCAL_DIR := $(call my-dir) -include $(HIERARCHYVIEWERAPP_LOCAL_DIR)/etc/Android.mk -include $(HIERARCHYVIEWERAPP_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +LOCAL_JAVA_LIBRARIES := \ + ddmlib \ + ddmuilib \ + hierarchyviewerlib \ + swt \ + org.eclipse.jface_3.4.2.M20090107-0800 \ + org.eclipse.core.commands_3.4.0.I20080509-2000 \ + sdklib \ + swtmenubar + +LOCAL_MODULE := hierarchyviewer2 + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/hierarchyviewer2/app/NOTICE b/hierarchyviewer2/app/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/hierarchyviewer2/app/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/hierarchyviewer2/app/README b/hierarchyviewer2/app/README new file mode 100755 index 0000000..c00ef99 --- /dev/null +++ b/hierarchyviewer2/app/README @@ -0,0 +1,69 @@ +Using the Eclipse project HierarchyViewer +----------------------------------------- + +HierarchyViewer requires some external libraries to compile. +If you build HierarchyViewer using the makefile, you have nothing +to configure. However if you want to develop on HierarchyViewer +using Eclipse, you need to perform the following configuration. + + +------- +1- Projects required in Eclipse +------- + +To run HierarchyViewer from Eclipse, you need to import the following 5 projects: + + - sdk/hierarchyviewer2/app + - sdk/hierarchyviewer2/libs/hierarchyviewerlib/ + - sdk/ddms/libs/ddmlib + - sdk/ddms/libs/ddmuilib + - sdk/sdkmanager/libs/sdklib + + +------- +2- HierarchyViewer requires some SWT JARs to compile. +------- + +SWT is available in the tree under prebuild/<platform>/swt + +Because the build path cannot contain relative path that are not inside +the project directory, the .classpath file references a user library +called ANDROID_SWT. + +In order to compile the project: +- Open Preferences > Java > Build Path > User Libraries + +- Create a new user library named ANDROID_SWT +- Add the following 4 JAR files: + + - prebuilt/<platform>/swt/swt.jar + - prebuilt/common/eclipse/org.eclipse.core.commands_3.*.jar + - prebuilt/common/eclipse/org.eclipse.equinox.common_3.*.jar + - prebuilt/common/eclipse/org.eclipse.jface_3.*.jar + + +------- +3- HierarchyViewer also requires the compiled SwtMenuBar library. +------- + +Build the swtmenubar library: +$ cd $TOP (top of Android tree) +$ . build/envsetup.sh && lunch sdk-eng +$ sdk/eclipse/scripts/create_sdkman_symlinks.sh + +Define a classpath variable in Eclipse: +- Open Preferences > Java > Build Path > Classpath Variables +- Create a new classpath variable named ANDROID_OUT_FRAMEWORK +- Set its folder value to <Android tree>/out/host/<platform>/framework +- Create a new classpath variable named ANDROID_SRC +- Set its folder value to <Android tree> + +You might need to clean the ddms project (Project > Clean...) after +you add the new classpath variable, otherwise previous errors might not +go away automatically. + +The ANDROID_SRC part should be optional. It allows you to have access to +the SwtMenuBar generic parts from the Java editor. + +-- +EOF diff --git a/hierarchyviewer2/app/etc/hierarchyviewer b/hierarchyviewer2/app/etc/hierarchyviewer index 9f31701..82304dc 100755 --- a/hierarchyviewer2/app/etc/hierarchyviewer +++ b/hierarchyviewer2/app/etc/hierarchyviewer @@ -76,7 +76,7 @@ if [ `uname` = "Linux" ]; then export GDK_NATIVE_WINDOWS=true fi -jarpath="$frameworkdir/$jarfile" +jarpath="$frameworkdir/$jarfile:$frameworkdir/swtmenubar.jar" # Figure out the path to the swt.jar for the current architecture. # if ANDROID_SWT is defined, then just use this. @@ -103,4 +103,8 @@ fi # need to use "java.ext.dirs" because "-jar" causes classpath to be ignored # might need more memory, e.g. -Xmx128M -exec "$javaCmd" -Xmx512M $os_opts $java_debug -Dcom.android.hierarchyviewer.bindir="$progdir" -classpath "$jarpath:$swtpath/swt.jar" com.android.hierarchyviewer.HierarchyViewerApplication "$@" +exec "$javaCmd" \ + -Xmx512M $os_opts $java_debug \ + -Dcom.android.hierarchyviewer.bindir="$progdir" \ + -classpath "$jarpath:$swtpath/swt.jar" \ + com.android.hierarchyviewer.HierarchyViewerApplication "$@" diff --git a/hierarchyviewer2/app/etc/hierarchyviewer.bat b/hierarchyviewer2/app/etc/hierarchyviewer.bat index 0876d2f..8a14957 100755 --- a/hierarchyviewer2/app/etc/hierarchyviewer.bat +++ b/hierarchyviewer2/app/etc/hierarchyviewer.bat @@ -49,7 +49,7 @@ if debug NEQ "%1" goto NoDebug shift 1
:NoDebug
-set jarpath=%frameworkdir%%jarfile%;%frameworkdir%hierarchyviewerlib.jar
+set jarpath=%frameworkdir%%jarfile%;%frameworkdir%hierarchyviewerlib.jar;%frameworkdir%swtmenubar.jar
if not defined ANDROID_SWT goto QueryArch
set swt_path=%ANDROID_SWT%
diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/AboutDialog.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/AboutDialog.java index 3f973e7..150c70a 100644 --- a/hierarchyviewer2/app/src/com/android/hierarchyviewer/AboutDialog.java +++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/AboutDialog.java @@ -41,8 +41,8 @@ public class AboutDialog extends Dialog { public AboutDialog(Shell shell) { super(shell); ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class); - mSmallImage = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$ - mAboutImage = imageLoader.loadImage("about.jpg", Display.getDefault()); //$NON-NLS-1$ + mSmallImage = imageLoader.loadImage("sdk-hierarchyviewer-16.png", Display.getDefault()); //$NON-NLS-1$ + mAboutImage = imageLoader.loadImage("sdk-hierarchyviewer-128.png", Display.getDefault()); //$NON-NLS-1$ } @Override diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java index 25943c4..bf18965 100644 --- a/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java +++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/HierarchyViewerApplication.java @@ -26,6 +26,7 @@ import com.android.hierarchyviewer.util.ActionButton; import com.android.hierarchyviewerlib.HierarchyViewerDirector; import com.android.hierarchyviewerlib.actions.CapturePSDAction; import com.android.hierarchyviewerlib.actions.DisplayViewAction; +import com.android.hierarchyviewerlib.actions.DumpDisplayListAction; import com.android.hierarchyviewerlib.actions.InspectScreenshotAction; import com.android.hierarchyviewerlib.actions.InvalidateAction; import com.android.hierarchyviewerlib.actions.LoadOverlayAction; @@ -38,6 +39,8 @@ import com.android.hierarchyviewerlib.actions.RefreshWindowsAction; import com.android.hierarchyviewerlib.actions.RequestLayoutAction; import com.android.hierarchyviewerlib.actions.SavePixelPerfectAction; import com.android.hierarchyviewerlib.actions.SaveTreeViewAction; +import com.android.hierarchyviewerlib.device.DeviceBridge.ViewServerInfo; +import com.android.hierarchyviewerlib.models.DeviceSelectionModel; import com.android.hierarchyviewerlib.models.PixelPerfectModel; import com.android.hierarchyviewerlib.models.TreeViewModel; import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener; @@ -53,11 +56,15 @@ import com.android.hierarchyviewerlib.ui.PropertyViewer; import com.android.hierarchyviewerlib.ui.TreeView; import com.android.hierarchyviewerlib.ui.TreeViewControls; import com.android.hierarchyviewerlib.ui.TreeViewOverview; +import com.android.menubar.IMenuBarEnhancer; +import com.android.menubar.MenuBarEnhancer; +import com.android.menubar.IMenuBarEnhancer.MenuBarMode; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.window.ApplicationWindow; import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.SelectionEvent; @@ -81,8 +88,9 @@ import org.eclipse.swt.widgets.Shell; public class HierarchyViewerApplication extends ApplicationWindow { - private static final int INITIAL_WIDTH = 1024; - private static final int INITIAL_HEIGHT = 768; + private static final String APP_NAME = "Hierarchy Viewer"; + private static final int INITIAL_WIDTH = 1280; + private static final int INITIAL_HEIGHT = 800; private static HierarchyViewerApplication sMainWindow; @@ -119,6 +127,8 @@ public class HierarchyViewerApplication extends ApplicationWindow { private PixelPerfectLoupe mPixelPerfectLoupe; private Composite mTreeViewControls; + private ActionButton dumpDisplayList; + private HierarchyViewerDirector mDirector; /* @@ -148,9 +158,9 @@ public class HierarchyViewerApplication extends ApplicationWindow { @Override protected void configureShell(Shell shell) { super.configureShell(shell); - shell.setText("Hierarchy Viewer"); + shell.setText(APP_NAME); ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class); - Image image = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$ + Image image = imageLoader.loadImage("sdk-hierarchyviewer-128.png", Display.getDefault()); //$NON-NLS-1$ shell.setImage(image); } @@ -162,7 +172,14 @@ public class HierarchyViewerApplication extends ApplicationWindow { public void run() { setBlockOnOpen(true); - open(); + try { + open(); + } catch (SWTException e) { + // Ignore "widget disposed" errors after we closed. + if (!getShell().isDisposed()) { + throw e; + } + } TreeViewModel.getModel().removeTreeChangeListener(mTreeChangeListener); PixelPerfectModel.getModel().removeImageChangeListener(mImageChangeListener); @@ -354,7 +371,7 @@ public class HierarchyViewerApplication extends ApplicationWindow { Composite innerButtonPanel = new Composite(buttonPanel, SWT.NONE); innerButtonPanel.setLayoutData(new GridData(GridData.FILL_VERTICAL)); - GridLayout innerButtonPanelLayout = new GridLayout(6, true); + GridLayout innerButtonPanelLayout = new GridLayout(7, true); innerButtonPanelLayout.marginWidth = innerButtonPanelLayout.marginHeight = 2; innerButtonPanelLayout.horizontalSpacing = innerButtonPanelLayout.verticalSpacing = 2; innerButtonPanel.setLayout(innerButtonPanelLayout); @@ -382,6 +399,10 @@ public class HierarchyViewerApplication extends ApplicationWindow { new ActionButton(innerButtonPanel, RequestLayoutAction.getAction()); requestLayout.setLayoutData(new GridData(GridData.FILL_BOTH)); + dumpDisplayList = + new ActionButton(innerButtonPanel, DumpDisplayListAction.getAction()); + dumpDisplayList.setLayoutData(new GridData(GridData.FILL_BOTH)); + SashForm mainSash = new SashForm(mTreeViewPanel, SWT.HORIZONTAL | SWT.SMOOTH); mainSash.setLayoutData(new GridData(GridData.FILL_BOTH)); Composite treeViewContainer = new Composite(mainSash, SWT.BORDER); @@ -581,12 +602,16 @@ public class HierarchyViewerApplication extends ApplicationWindow { MenuManager mm = getMenuBarManager(); mm.removeAll(); - String os = System.getProperty("os.name"); //$NON-NLS-1$ - if (os.startsWith("Mac OS") == false) { //$NON-NLS-1$ - MenuManager file = new MenuManager("&File"); + MenuManager file = new MenuManager("&File"); + IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenuManager( + APP_NAME, + getShell().getDisplay(), + file, + AboutAction.getAction(getShell()), + null /*preferencesAction*/, + QuitAction.getAction()); + if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) { mm.add(file); - - file.add(QuitAction.getAction()); } MenuManager device = new MenuManager("&Devices"); @@ -596,11 +621,6 @@ public class HierarchyViewerApplication extends ApplicationWindow { device.add(LoadViewHierarchyAction.getAction()); device.add(InspectScreenshotAction.getAction()); - MenuManager help = new MenuManager("&Help"); - mm.add(help); - - help.add(AboutAction.getAction(getShell())); - mm.updateAll(true); mDeviceViewButton.setSelection(true); @@ -626,12 +646,16 @@ public class HierarchyViewerApplication extends ApplicationWindow { MenuManager mm = getMenuBarManager(); mm.removeAll(); - String os = System.getProperty("os.name"); //$NON-NLS-1$ - if (os.startsWith("Mac OS") == false) { //$NON-NLS-1$ - MenuManager file = new MenuManager("&File"); + MenuManager file = new MenuManager("&File"); + IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenuManager( + APP_NAME, + getShell().getDisplay(), + file, + AboutAction.getAction(getShell()), + null /*preferencesAction*/, + QuitAction.getAction()); + if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) { mm.add(file); - - file.add(QuitAction.getAction()); } MenuManager treeViewMenu = new MenuManager("&Tree View"); @@ -642,15 +666,18 @@ public class HierarchyViewerApplication extends ApplicationWindow { treeViewMenu.add(new Separator()); treeViewMenu.add(RefreshViewAction.getAction()); treeViewMenu.add(DisplayViewAction.getAction(getShell())); + // Only make the DumpDisplayList action visible if the protocol supports it. + ViewServerInfo info = DeviceSelectionModel.getModel().getSelectedDeviceInfo(); + if (info != null && info.protocolVersion >= 4) { + treeViewMenu.add(DumpDisplayListAction.getAction()); + dumpDisplayList.setVisible(true); + } else { + dumpDisplayList.setVisible(false); + } treeViewMenu.add(new Separator()); treeViewMenu.add(InvalidateAction.getAction()); treeViewMenu.add(RequestLayoutAction.getAction()); - MenuManager help = new MenuManager("&Help"); - mm.add(help); - - help.add(AboutAction.getAction(getShell())); - mm.updateAll(true); mDeviceViewButton.setSelection(false); @@ -676,12 +703,16 @@ public class HierarchyViewerApplication extends ApplicationWindow { MenuManager mm = getMenuBarManager(); mm.removeAll(); - String os = System.getProperty("os.name"); //$NON-NLS-1$ - if (os.startsWith("Mac OS") == false) { //$NON-NLS-1$ - MenuManager file = new MenuManager("&File"); + MenuManager file = new MenuManager("&File"); + IMenuBarEnhancer enhancer = MenuBarEnhancer.setupMenuManager( + APP_NAME, + getShell().getDisplay(), + file, + AboutAction.getAction(getShell()), + null /*preferencesAction*/, + QuitAction.getAction()); + if (enhancer.getMenuBarMode() == MenuBarMode.GENERIC) { mm.add(file); - - file.add(QuitAction.getAction()); } MenuManager pixelPerfect = new MenuManager("&Pixel Perfect"); @@ -695,11 +726,6 @@ public class HierarchyViewerApplication extends ApplicationWindow { mm.add(pixelPerfect); - MenuManager help = new MenuManager("&Help"); - mm.add(help); - - help.add(AboutAction.getAction(getShell())); - mm.updateAll(true); mDeviceViewButton.setSelection(false); diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/actions/AboutAction.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/actions/AboutAction.java index 0c7c7b2..332b2dc 100644 --- a/hierarchyviewer2/app/src/com/android/hierarchyviewer/actions/AboutAction.java +++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/actions/AboutAction.java @@ -41,7 +41,7 @@ public class AboutAction extends Action implements ImageAction { this.mShell = shell; setAccelerator(SWT.MOD1 + 'A'); ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class); - mImage = imageLoader.loadImage("about-small.jpg", Display.getDefault()); //$NON-NLS-1$ + mImage = imageLoader.loadImage("sdk-hierarchyviewer-16.png", Display.getDefault()); //$NON-NLS-1$ setImageDescriptor(ImageDescriptor.createFromImage(mImage)); setToolTipText("Shows the about dialog"); } diff --git a/hierarchyviewer2/app/src/com/android/hierarchyviewer/util/ActionButton.java b/hierarchyviewer2/app/src/com/android/hierarchyviewer/util/ActionButton.java index 4681c40..ca3c689 100644 --- a/hierarchyviewer2/app/src/com/android/hierarchyviewer/util/ActionButton.java +++ b/hierarchyviewer2/app/src/com/android/hierarchyviewer/util/ActionButton.java @@ -73,4 +73,8 @@ public class ActionButton implements IPropertyChangeListener, SelectionListener public void addSelectionListener(SelectionListener listener) { mButton.addSelectionListener(listener); } + + public void setVisible(boolean visible) { + mButton.setVisible(visible); + } } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/NOTICE b/hierarchyviewer2/libs/hierarchyviewerlib/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk index ded20e1..3ca63dd 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/Android.mk @@ -16,7 +16,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := resources +LOCAL_JAVA_RESOURCE_DIRS := ../src LOCAL_JAR_MANIFEST := ../manifest.txt diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java index 77f8d74..23dfbea 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/HierarchyViewerDirector.java @@ -166,7 +166,7 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener, return; } Window[] windows = DeviceBridge.loadWindows(device); - DeviceSelectionModel.getModel().addDevice(device, windows); + DeviceSelectionModel.getModel().addDevice(device, windows, viewServerInfo); if (viewServerInfo.protocolVersion >= 3) { WindowUpdater.startListenForWindowChanges(HierarchyViewerDirector.this, device); focusChanged(device); @@ -586,6 +586,17 @@ public abstract class HierarchyViewerDirector implements IDeviceChangeListener, } } + public void dumpDisplayListForCurrentNode() { + final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection(); + if (selectedNode != null) { + executeInBackground("Dump displaylist", new Runnable() { + public void run() { + DeviceBridge.outputDisplayList(selectedNode.viewNode); + } + }); + } + } + public void loadAllViews() { executeInBackground("Loading all views", new Runnable() { public void run() { diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/DumpDisplayListAction.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/DumpDisplayListAction.java new file mode 100644 index 0000000..8b9ba29 --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/actions/DumpDisplayListAction.java @@ -0,0 +1,56 @@ +/* + * 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.hierarchyviewerlib.actions; + +import com.android.ddmuilib.ImageLoader; +import com.android.hierarchyviewerlib.HierarchyViewerDirector; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; + +public class DumpDisplayListAction extends SelectedNodeEnabledAction implements ImageAction { + + private static DumpDisplayListAction sAction; + + private Image mImage; + + private DumpDisplayListAction() { + super("Dump DisplayList"); + ImageLoader imageLoader = ImageLoader.getLoader(HierarchyViewerDirector.class); + mImage = imageLoader.loadImage("load-view-hierarchy.png", Display.getDefault()); //$NON-NLS-1$ + setImageDescriptor(ImageDescriptor.createFromImage(mImage)); + setToolTipText("Request the view to output its displaylist to logcat"); + } + + public static DumpDisplayListAction getAction() { + if (sAction == null) { + sAction = new DumpDisplayListAction(); + } + return sAction; + } + + @Override + public void run() { + HierarchyViewerDirector.getDirector().dumpDisplayListForCurrentNode(); + } + + public Image getImage() { + return mImage; + } +} diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java index 40cc3a9..610f7b3 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/device/DeviceBridge.java @@ -643,4 +643,18 @@ public class DeviceBridge { } } + public static void outputDisplayList(ViewNode viewNode) { + DeviceConnection connection = null; + try { + connection = new DeviceConnection(viewNode.window.getDevice()); + connection.sendCommand("OUTPUT_DISPLAYLIST " + + viewNode.window.encode() + " " + viewNode); //$NON-NLS-1$ + } catch (Exception e) { + Log.e(TAG, "Unable to dump displaylist for node " + viewNode + " in window " + + viewNode.window + " on device " + viewNode.window.getDevice()); + } finally { + connection.close(); + } + } + } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java index d029d39..b00a1dc 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/com/android/hierarchyviewerlib/models/DeviceSelectionModel.java @@ -17,6 +17,7 @@ package com.android.hierarchyviewerlib.models; import com.android.ddmlib.IDevice; +import com.android.hierarchyviewerlib.device.DeviceBridge.ViewServerInfo; import com.android.hierarchyviewerlib.device.Window; import java.util.ArrayList; @@ -29,7 +30,7 @@ import java.util.HashMap; */ public class DeviceSelectionModel { - private final HashMap<IDevice, Window[]> mDeviceMap = new HashMap<IDevice, Window[]>(); + private final HashMap<IDevice, DeviceInfo> mDeviceMap = new HashMap<IDevice, DeviceInfo>(); private final HashMap<IDevice, Integer> mFocusedWindowHashes = new HashMap<IDevice, Integer>(); @@ -44,6 +45,15 @@ public class DeviceSelectionModel { private static DeviceSelectionModel sModel; + private static class DeviceInfo { + Window[] windows; + ViewServerInfo viewServerInfo; + + private DeviceInfo(Window[] windows, ViewServerInfo viewServerInfo) { + this.windows = windows; + this.viewServerInfo = viewServerInfo; + } + } public static DeviceSelectionModel getModel() { if (sModel == null) { sModel = new DeviceSelectionModel(); @@ -57,9 +67,9 @@ public class DeviceSelectionModel { } } - public void addDevice(IDevice device, Window[] windows) { + public void addDevice(IDevice device, Window[] windows, ViewServerInfo info) { synchronized (mDeviceMap) { - mDeviceMap.put(device, windows); + mDeviceMap.put(device, new DeviceInfo(windows, info)); mDeviceList.add(device); } notifyDeviceConnected(device); @@ -88,7 +98,12 @@ public class DeviceSelectionModel { public void updateDevice(IDevice device, Window[] windows) { boolean selectionChanged = false; synchronized (mDeviceMap) { - mDeviceMap.put(device, windows); + DeviceInfo oldDeviceInfo = mDeviceMap.get(device); + ViewServerInfo oldViewServerInfo = null; + if (oldDeviceInfo != null) { + oldViewServerInfo = oldDeviceInfo.viewServerInfo; + } + mDeviceMap.put(device, new DeviceInfo(windows, oldViewServerInfo)); // If the selected window no longer exists, we clear the selection. if (mSelectedDevice == device && mSelectedWindow != null) { boolean windowStillExists = false; @@ -214,9 +229,12 @@ public class DeviceSelectionModel { } public Window[] getWindows(IDevice device) { - Window[] windows; + Window[] windows = null; synchronized (mDeviceMap) { - windows = mDeviceMap.get(device); + DeviceInfo info = mDeviceMap.get(device); + if (info != null) { + windows = mDeviceMap.get(device).windows; + } } return windows; } @@ -253,4 +271,15 @@ public class DeviceSelectionModel { return mSelectedWindow; } } + + public ViewServerInfo getSelectedDeviceInfo() { + synchronized (mDeviceMap) { + ViewServerInfo viewServerInfo = null; + if (mSelectedDevice != null) { + return mDeviceMap.get(mSelectedDevice).viewServerInfo; + } + return null; + } + } + } diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/auto-refresh.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/auto-refresh.png Binary files differindex 240862f..240862f 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/auto-refresh.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/auto-refresh.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/capture-psd.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/capture-psd.png Binary files differindex 0f25426..0f25426 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/capture-psd.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/capture-psd.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/device-view-selected.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/device-view-selected.png Binary files differindex fd107ed..fd107ed 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/device-view-selected.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/device-view-selected.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/device-view.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/device-view.png Binary files differindex 9a7eed4..9a7eed4 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/device-view.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/device-view.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/display.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/display.png Binary files differindex a9de0ec..a9de0ec 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/display.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/display.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/filtered.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/filtered.png Binary files differindex 4fcab3f..4fcab3f 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/filtered.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/filtered.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/green.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/green.png Binary files differindex 800000d..800000d 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/green.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/green.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/inspect-screenshot.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/inspect-screenshot.png Binary files differindex 6e51701..6e51701 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/inspect-screenshot.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/inspect-screenshot.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/invalidate.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/invalidate.png Binary files differindex ee75f69..ee75f69 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/invalidate.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/invalidate.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/load-all-views.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/load-all-views.png Binary files differindex 3329ec9..3329ec9 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/load-all-views.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/load-all-views.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/load-overlay.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/load-overlay.png Binary files differindex 4817252..4817252 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/load-overlay.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/load-overlay.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/load-view-hierarchy.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/load-view-hierarchy.png Binary files differindex 8f01dda..8f01dda 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/load-view-hierarchy.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/load-view-hierarchy.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/not-selected.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/not-selected.png Binary files differindex db6f13b..db6f13b 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/not-selected.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/not-selected.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/on-black.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/on-black.png Binary files differindex cd88803..cd88803 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/on-black.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/on-black.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/on-white.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/on-white.png Binary files differindex 5f05662..5f05662 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/on-white.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/on-white.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/pixel-perfect-view-selected.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/pixel-perfect-view-selected.png Binary files differindex 1e44000..1e44000 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/pixel-perfect-view-selected.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/pixel-perfect-view-selected.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/pixel-perfect-view.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/pixel-perfect-view.png Binary files differindex ec51cec..ec51cec 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/pixel-perfect-view.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/pixel-perfect-view.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/red.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/red.png Binary files differindex a2ab855..a2ab855 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/red.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/red.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/refresh-windows.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/refresh-windows.png Binary files differindex 8fddcae..8fddcae 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/refresh-windows.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/refresh-windows.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/request-layout.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/request-layout.png Binary files differindex 92a78c8..92a78c8 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/request-layout.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/request-layout.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/save.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/save.png Binary files differindex 2c0bab1..2c0bab1 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/save.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/save.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/images/sdk-hierarchyviewer-128.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/sdk-hierarchyviewer-128.png Binary files differnew file mode 100644 index 0000000..4535f22 --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/sdk-hierarchyviewer-128.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/images/sdk-hierarchyviewer-16.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/sdk-hierarchyviewer-16.png Binary files differnew file mode 100755 index 0000000..8c3c23d --- /dev/null +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/sdk-hierarchyviewer-16.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected-filtered-small.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected-filtered-small.png Binary files differindex 9ef6b34..9ef6b34 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected-filtered-small.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected-filtered-small.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected-filtered.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected-filtered.png Binary files differindex 1f59685..1f59685 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected-filtered.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected-filtered.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected-small.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected-small.png Binary files differindex 538e385..538e385 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected-small.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected-small.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected.png Binary files differindex 5cd5c3f..5cd5c3f 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/selected.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/selected.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-extras.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/show-extras.png Binary files differindex ba9c305..ba9c305 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-extras.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/show-extras.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-overlay.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/show-overlay.png Binary files differindex e39e90a..e39e90a 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/show-overlay.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/show-overlay.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/tree-view-selected.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/tree-view-selected.png Binary files differindex 175ad1f..175ad1f 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/tree-view-selected.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/tree-view-selected.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/tree-view.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/tree-view.png Binary files differindex 23aa424..23aa424 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/tree-view.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/tree-view.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/yellow.png b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/yellow.png Binary files differindex e9b5781..e9b5781 100644 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/yellow.png +++ b/hierarchyviewer2/libs/hierarchyviewerlib/src/images/yellow.png diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/about-small.jpg b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/about-small.jpg Binary files differdeleted file mode 100644 index 6fe9291..0000000 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/about-small.jpg +++ /dev/null diff --git a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/about.jpg b/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/about.jpg Binary files differdeleted file mode 100644 index 8e10514..0000000 --- a/hierarchyviewer2/libs/hierarchyviewerlib/src/resources/images/about.jpg +++ /dev/null diff --git a/ide_common/.settings/org.eclipse.jdt.core.prefs b/ide_common/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/ide_common/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/ide_common/NOTICE b/ide_common/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/ide_common/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/ide_common/src/com/android/ide/common/rendering/LayoutLibrary.java b/ide_common/src/com/android/ide/common/rendering/LayoutLibrary.java index d9ffda7..e1256fe 100644 --- a/ide_common/src/com/android/ide/common/rendering/LayoutLibrary.java +++ b/ide_common/src/com/android/ide/common/rendering/LayoutLibrary.java @@ -16,6 +16,8 @@ package com.android.ide.common.rendering; +import static com.android.ide.common.rendering.api.Result.Status.NOT_IMPLEMENTED; + import com.android.ide.common.log.ILogger; import com.android.ide.common.rendering.api.Bridge; import com.android.ide.common.rendering.api.Capability; @@ -321,6 +323,55 @@ public class LayoutLibrary { } } + /** + * Utility method returning the parent of a given view object. + * + * @param viewObject the object for which to return the parent. + * + * @return a {@link Result} indicating the status of the action, and if success, the parent + * object in {@link Result#getData()} + */ + public Result getViewParent(Object viewObject) { + if (mBridge != null) { + return mBridge.getViewParent(viewObject); + } + + return NOT_IMPLEMENTED.createResult(); + } + + /** + * Utility method returning the index of a given view in its parent. + * @param viewObject the object for which to return the index. + * + * @return a {@link Result} indicating the status of the action, and if success, the index in + * the parent in {@link Result#getData()} + */ + public Result getViewIndex(Object viewObject) { + if (mBridge != null) { + return mBridge.getViewIndex(viewObject); + } + + return NOT_IMPLEMENTED.createResult(); + } + + /** + * Utility method returning the baseline value for a given view object. This basically returns + * View.getBaseline(). + * + * @param viewObject the object for which to return the index. + * + * @return the baseline value or -1 if not applicable to the view object or if this layout + * library does not implement this method. + */ + public int getViewBaseline(Object viewObject) { + if (mBridge != null) { + return mBridge.getViewBaseline(viewObject); + } + + return -1; + } + + // ------ Implementation private LayoutLibrary(Bridge bridge, ILayoutBridge legacyBridge, ClassLoader classLoader, @@ -462,7 +513,7 @@ public class LayoutLibrary { for (Entry<ResourceType, Map<String, ResourceValue>> entry : map.entrySet()) { // ugly case but works. result.put(entry.getKey().getName(), - (Map<String, IResourceValue>)(Map) entry.getValue()); + (Map) entry.getValue()); } return result; diff --git a/ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java b/ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java new file mode 100644 index 0000000..70bbcef --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/FrameworkResourceItem.java @@ -0,0 +1,40 @@ +/* + * 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.ide.common.resources; + +/** + * A custom {@link ResourceItem} for resources provided by the framework. + * + * The main change is that {@link #isEditableDirectly()} returns false. + */ +class FrameworkResourceItem extends ResourceItem { + + FrameworkResourceItem(String name) { + super(name); + } + + @Override + public boolean isEditableDirectly() { + return false; + } + + @Override + public String toString() { + return "FrameworkResourceItem [mName=" + getName() + ", mFiles=" //$NON-NLS-1$ //$NON-NLS-2$ + + getSourceFileList() + "]"; //$NON-NLS-1$ + } +} diff --git a/ide_common/src/com/android/ide/common/resources/FrameworkResources.java b/ide_common/src/com/android/ide/common/resources/FrameworkResources.java new file mode 100644 index 0000000..31dc137 --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/FrameworkResources.java @@ -0,0 +1,202 @@ +/* + * 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.ide.common.resources; + +import static com.android.AndroidConstants.FD_RES_VALUES; + +import com.android.ide.common.log.ILogger; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.resources.ResourceType; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +/** + * Framework resources repository. + * + * This behaves the same as {@link ResourceRepository} except that it differentiates between + * resources that are public and non public. + * {@link #getResources(ResourceType)} and {@link #hasResourcesOfType(ResourceType)} only return + * public resources. This is typically used to display resource lists in the UI. + * + * {@link #getConfiguredResources(com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration)} + * returns all resources, even the non public ones so that this can be used for rendering. + */ +public class FrameworkResources extends ResourceRepository { + + /** + * Map of {@link ResourceType} to list of items. It is guaranteed to contain a list for all + * possible values of ResourceType. + */ + protected final Map<ResourceType, List<ResourceItem>> mPublicResourceMap = + new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class); + + public FrameworkResources() { + super(true /*isFrameworkRepository*/); + } + + /** + * Returns a {@link Collection} (always non null, but can be empty) of <b>public</b> + * {@link ResourceItem} matching a given {@link ResourceType}. + * + * @param type the type of the resources to return + * @return a collection of items, possible empty. + */ + @Override + public List<ResourceItem> getResourceItemsOfType(ResourceType type) { + return mPublicResourceMap.get(type); + } + + /** + * Returns whether the repository has <b>public</b> resources of a given {@link ResourceType}. + * @param type the type of resource to check. + * @return true if the repository contains resources of the given type, false otherwise. + */ + @Override + public boolean hasResourcesOfType(ResourceType type) { + return mPublicResourceMap.get(type).size() > 0; + } + + @Override + protected ResourceItem createResourceItem(String name) { + return new FrameworkResourceItem(name); + } + + /** + * Reads the public.xml file in data/res/values/ for a given resource folder and builds up + * a map of public resources. + * + * This map is a subset of the full resource map that only contains framework resources + * that are public. + * + * @param osFrameworkResourcePath The root folder of the resources + */ + public void loadPublicResources(IAbstractFolder resFolder, ILogger logger) { + IAbstractFolder valueFolder = resFolder.getFolder(FD_RES_VALUES); + if (valueFolder.exists() == false) { + return; + } + + IAbstractFile publicXmlFile = valueFolder.getFile("public.xml"); //$NON-NLS-1$ + if (publicXmlFile.exists()) { + Document document = null; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + Reader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(publicXmlFile.getContents())); + InputSource is = new InputSource(reader); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + document = builder.parse(is); + + ResourceType lastType = null; + String lastTypeName = ""; + + NodeList children = document.getDocumentElement().getChildNodes(); + for (int i = 0, n = children.getLength(); i < n; i++) { + Node node = children.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + String name = element.getAttribute("name"); //$NON-NLS-1$ + if (name.length() > 0) { + String typeName = element.getAttribute("type"); //$NON-NLS-1$ + ResourceType type = null; + if (typeName.equals(lastTypeName)) { + type = lastType; + } else { + type = ResourceType.getEnum(typeName); + lastType = type; + lastTypeName = typeName; + } + if (type != null) { + List<ResourceItem> typeList = mResourceMap.get(type); + + ResourceItem match = null; + if (typeList != null) { + for (ResourceItem item : typeList) { + if (name.equals(item.getName())) { + match = item; + break; + } + } + } + + if (match != null) { + List<ResourceItem> publicList = mPublicResourceMap.get(type); + if (publicList == null) { + publicList = new ArrayList<ResourceItem>(); + mPublicResourceMap.put(type, publicList); + } + + publicList.add(match); + } else { + // log that there's a public resource that doesn't actually + // exist? + } + } + } + } + } + } catch (Exception e) { + if (logger != null) { + logger.error(e, "Can't read and parse public attribute list"); + } + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // Nothing to be done here - we don't care if it closed or not. + } + } + } + } + + // put unmodifiable list for all res type in the public resource map + // this will simplify access + for (ResourceType type : ResourceType.values()) { + List<ResourceItem> list = mPublicResourceMap.get(type); + if (list == null) { + list = Collections.emptyList(); + } else { + list = Collections.unmodifiableList(list); + } + + // put the new list in the map + mPublicResourceMap.put(type, list); + } + } +} diff --git a/ide_common/src/com/android/ide/common/resources/InlineResourceItem.java b/ide_common/src/com/android/ide/common/resources/InlineResourceItem.java new file mode 100644 index 0000000..37fdc81 --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/InlineResourceItem.java @@ -0,0 +1,71 @@ +/* + * 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.ide.common.resources; + +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.resources.ResourceType; + + +/** + * Represents a resource item that has been declared inline in another resource file. + * + * This covers the typical ID declaration of "@+id/foo", but does not cover normal value + * resources declared in strings.xml or other similar value files. + * + * This resource will return {@code true} for {@link #isDeclaredInline()} and {@code false} for + * {@link #isEditableDirectly()}. + */ +public class InlineResourceItem extends ResourceItem { + + private ResourceValue mValue = null; + + /** + * Constructs a new inline ResourceItem. + * @param name the name of the resource as it appears in the XML and R.java files. + */ + public InlineResourceItem(String name) { + super(name); + } + + @Override + public boolean isDeclaredInline() { + return true; + } + + @Override + public boolean isEditableDirectly() { + return false; + } + + @Override + public ResourceValue getResourceValue(ResourceType type, FolderConfiguration referenceConfig, + boolean isFramework) { + assert type == ResourceType.ID; + if (mValue == null) { + mValue = new ResourceValue(type, getName(), isFramework); + } + + return mValue; + } + + @Override + public String toString() { + return "InlineResourceItem [mName=" + getName() + ", mFiles=" //$NON-NLS-1$ //$NON-NLS-2$ + + getSourceFileList() + "]"; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IntArrayWrapper.java b/ide_common/src/com/android/ide/common/resources/IntArrayWrapper.java index 3e614ed..668c677 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/IntArrayWrapper.java +++ b/ide_common/src/com/android/ide/common/resources/IntArrayWrapper.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.ide.common.resources; import java.util.Arrays; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/MultiResourceFile.java b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java index a3223c0..c6bfeff 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/MultiResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/MultiResourceFile.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,24 +14,22 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.ide.common.resources; import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.common.resources.ValueResourceParser; import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository; +import com.android.io.IAbstractFile; +import com.android.io.StreamException; import com.android.resources.ResourceType; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.StreamException; import org.xml.sax.SAXException; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; -import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; @@ -47,72 +45,79 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance(); - private final Map<ResourceType, HashMap<String, ResourceValue>> mResourceItems = - new EnumMap<ResourceType, HashMap<String, ResourceValue>>(ResourceType.class); + private final Map<ResourceType, Map<String, ResourceValue>> mResourceItems = + new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class); + + private Collection<ResourceType> mResourceTypeList = null; public MultiResourceFile(IAbstractFile file, ResourceFolder folder) { super(file, folder); } @Override - public ResourceType[] getResourceTypes() { - update(); + protected void load() { + // need to parse the file and find the content. + parseFile(); - Set<ResourceType> keys = mResourceItems.keySet(); + // create new ResourceItems for the new content. + mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet()); - return keys.toArray(new ResourceType[keys.size()]); + // create/update the resource items. + updateResourceItems(); } @Override - public boolean hasResources(ResourceType type) { - update(); + protected void update() { + // remove this file from all existing ResourceItem. + getFolder().getRepository().removeFile(mResourceTypeList, this); - HashMap<String, ResourceValue> list = mResourceItems.get(type); - return (list != null && list.size() > 0); - } + // reset current content. + mResourceItems.clear(); - @Override - public Collection<ProjectResourceItem> getResources(ResourceType type, - ProjectResources projectResources) { - update(); + // need to parse the file and find the content. + parseFile(); - HashMap<String, ResourceValue> list = mResourceItems.get(type); + // create new ResourceItems for the new content. + mResourceTypeList = Collections.unmodifiableCollection(mResourceItems.keySet()); - ArrayList<ProjectResourceItem> items = new ArrayList<ProjectResourceItem>(); + // create/update the resource items. + updateResourceItems(); + } - if (list != null) { - Collection<ResourceValue> values = list.values(); - for (ResourceValue res : values) { - ProjectResourceItem item = projectResources.findResourceItem(type, res.getName()); - - if (item == null) { - if (type == ResourceType.ID) { - item = new IdResourceItem(res.getName(), false /* isDeclaredInline */); - } else { - item = new ConfigurableResourceItem(res.getName()); - } - items.add(item); - } + @Override + protected void dispose() { + // only remove this file from all existing ResourceItem. + getFolder().getRepository().removeFile(mResourceTypeList, this); - item.add(this); - } - } + // don't need to touch the content, it'll get reclaimed as this objects disappear. + // In the mean time other objects may need to access it. + } - return items; + @Override + public Collection<ResourceType> getResourceTypes() { + return mResourceTypeList; } - /** - * Updates the Resource items if necessary. - */ - private void update() { - if (isTouched() == true) { - // reset current content. - mResourceItems.clear(); + @Override + public boolean hasResources(ResourceType type) { + Map<String, ResourceValue> list = mResourceItems.get(type); + return (list != null && list.size() > 0); + } + + private void updateResourceItems() { + ResourceRepository repository = getRepository(); + for (ResourceType type : mResourceTypeList) { + Map<String, ResourceValue> list = mResourceItems.get(type); - // need to parse the file and find the content. - parseFile(); + if (list != null) { + Collection<ResourceValue> values = list.values(); + for (ResourceValue res : values) { + ResourceItem item = repository.getResourceItem(type, res.getName()); - resetTouch(); + // add this file to the list of files generating this resource item. + item.add(this); + } + } } } @@ -136,7 +141,7 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou * @param value The value of the resource. */ public void addResourceValue(ResourceType resType, ResourceValue value) { - HashMap<String, ResourceValue> list = mResourceItems.get(resType); + Map<String, ResourceValue> list = mResourceItems.get(resType); // if the list does not exist, create it. if (list == null) { @@ -158,10 +163,8 @@ public final class MultiResourceFile extends ResourceFile implements IValueResou @Override public ResourceValue getValue(ResourceType type, String name) { - update(); - // get the list for the given type - HashMap<String, ResourceValue> list = mResourceItems.get(type); + Map<String, ResourceValue> list = mResourceItems.get(type); if (list != null) { return list.get(name); diff --git a/ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java b/ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java new file mode 100644 index 0000000..769b6ea --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/ResourceDeltaKind.java @@ -0,0 +1,26 @@ +/* + * 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.ide.common.resources; + +/** + * Enum indicating a type of resource change. + * + * This is similar, and can be easily mapped to Eclipse's integer constants in IResourceDelta. + */ +public enum ResourceDeltaKind { + CHANGED, ADDED, REMOVED; +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFile.java b/ide_common/src/com/android/ide/common/resources/ResourceFile.java index b754201..03f0b34 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceFile.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,19 +14,20 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.ide.common.resources; import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; +import com.android.ide.common.resources.configuration.Configurable; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.io.IAbstractFile; import com.android.resources.ResourceType; -import com.android.sdklib.io.IAbstractFile; import java.util.Collection; /** * Represents a Resource file (a file under $Project/res/) */ -public abstract class ResourceFile extends Resource { +public abstract class ResourceFile implements Configurable { private final IAbstractFile mFile; private final ResourceFolder mFolder; @@ -36,11 +37,10 @@ public abstract class ResourceFile extends Resource { mFolder = folder; } - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.Resource#getConfiguration() - */ - @Override + protected abstract void load(); + protected abstract void update(); + protected abstract void dispose(); + public FolderConfiguration getConfiguration() { return mFolder.getConfiguration(); } @@ -59,17 +59,21 @@ public abstract class ResourceFile extends Resource { return mFolder; } + public final ResourceRepository getRepository() { + return mFolder.getRepository(); + } + /** * Returns whether the resource is a framework resource. */ public final boolean isFramework() { - return mFolder.isFramework(); + return mFolder.getRepository().isFrameworkRepository(); } /** - * Returns the list of {@link ResourceType} generated by the file. + * Returns the list of {@link ResourceType} generated by the file. This is never null. */ - public abstract ResourceType[] getResourceTypes(); + public abstract Collection<ResourceType> getResourceTypes(); /** * Returns whether the file generated a resource of a specific type. @@ -78,18 +82,6 @@ public abstract class ResourceFile extends Resource { public abstract boolean hasResources(ResourceType type); /** - * Get the list of {@link ProjectResourceItem} of a specific type generated by the file. - * This method must make sure not to create duplicate. - * @param type The type of {@link ProjectResourceItem} to return. - * @param projectResources The global Project Resource object, allowing the implementation to - * query for already existing {@link ProjectResourceItem} - * @return The list of <b>new</b> {@link ProjectResourceItem} - * @see ProjectResources#findResourceItem(ResourceType, String) - */ - public abstract Collection<ProjectResourceItem> getResources(ResourceType type, - ProjectResources projectResources); - - /** * Returns the value of a resource generated by this file by {@link ResourceType} and name. * <p/>If no resource match, <code>null</code> is returned. * @param type the type of the resource. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolder.java b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java index b75f226..abdf200 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceFolder.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,31 +14,33 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.ide.common.resources; -import com.android.ide.eclipse.adt.internal.resources.ResourceItem; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.io.IFileWrapper; +import com.android.annotations.VisibleForTesting; +import com.android.annotations.VisibleForTesting.Visibility; +import com.android.ide.common.resources.configuration.Configurable; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.resources.FolderTypeRelationship; +import com.android.resources.ResourceFolderType; import com.android.resources.ResourceType; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * Resource Folder class. Contains list of {@link ResourceFile}s, - * the {@link FolderConfiguration}, and a link to the workspace {@link IFolder} object. + * the {@link FolderConfiguration}, and a link to the {@link IAbstractFolder} object. */ -public final class ResourceFolder extends Resource { - ResourceFolderType mType; - FolderConfiguration mConfiguration; +public final class ResourceFolder implements Configurable { + final ResourceFolderType mType; + final FolderConfiguration mConfiguration; IAbstractFolder mFolder; ArrayList<ResourceFile> mFiles = null; - private final boolean mIsFramework; + private final ResourceRepository mRepository; + /** * Creates a new {@link ResourceFolder} @@ -47,18 +49,65 @@ public final class ResourceFolder extends Resource { * @param folder The associated {@link IAbstractFolder} object. * @param isFrameworkRepository */ - public ResourceFolder(ResourceFolderType type, FolderConfiguration config, - IAbstractFolder folder, boolean isFrameworkRepository) { + protected ResourceFolder(ResourceFolderType type, FolderConfiguration config, + IAbstractFolder folder, ResourceRepository repository) { mType = type; mConfiguration = config; mFolder = folder; - mIsFramework = isFrameworkRepository; + mRepository = repository; } /** + * Processes a file and adds it to its parent folder resource. + * @param file the underlying resource file. + * @param folder the parent of the resource file. + * @param kind the file change kind. + * @return the {@link ResourceFile} that was created. + */ + public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind) { + // look for this file if it's already been created + ResourceFile resFile = getFile(file); + + if (resFile == null) { + if (kind != ResourceDeltaKind.REMOVED) { + // create a ResourceFile for it. + + // check if that's a single or multi resource type folder. For now we define this by + // the number of possible resource type output by files in the folder. This does + // not make the difference between several resource types from a single file or + // the ability to have 2 files in the same folder generating 2 different types of + // resource. The former is handled by MultiResourceFile properly while we don't + // handle the latter. If we were to add this behavior we'd have to change this call. + List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType); + + if (types.size() == 1) { + resFile = new SingleResourceFile(file, this); + } else { + resFile = new MultiResourceFile(file, this); + } + + resFile.load(); + + // add it to the folder + addFile(resFile); + } + } else { + if (kind == ResourceDeltaKind.REMOVED) { + removeFile(resFile); + } else { + resFile.update(); + } + } + + return resFile; + } + + + /** * Adds a {@link ResourceFile} to the folder. * @param file The {@link ResourceFile}. */ + @VisibleForTesting(visibility=Visibility.PROTECTED) public void addFile(ResourceFile file) { if (mFiles == null) { mFiles = new ArrayList<ResourceFile>(); @@ -67,35 +116,21 @@ public final class ResourceFolder extends Resource { mFiles.add(file); } - /** - * Attempts to remove the {@link ResourceFile} associated with a specified {@link IFile}. - * @param file the IFile object. - * @return the {@link ResourceFile} that was removed. - */ - public ResourceFile removeFile(IFile file) { - if (mFiles != null) { - int count = mFiles.size(); - for (int i = 0 ; i < count ; i++) { - ResourceFile resFile = mFiles.get(i); - if (resFile != null) { - IAbstractFile abstractFile = resFile.getFile(); - if (abstractFile instanceof IFileWrapper) { - IFile iFile = ((IFileWrapper)resFile.getFile()).getIFile(); - if (iFile != null && iFile.equals(file)) { - mFiles.remove(i); - touch(); - return resFile; - } - } - } - } + protected void removeFile(ResourceFile file) { + file.dispose(); + mFiles.remove(file); + } + + protected void dispose() { + for (ResourceFile file : mFiles) { + file.dispose(); } - return null; + mFiles.clear(); } /** - * Returns the {@link IFolder} associated with this object. + * Returns the {@link IAbstractFolder} associated with this object. */ public IAbstractFolder getFolder() { return mFolder; @@ -108,11 +143,8 @@ public final class ResourceFolder extends Resource { return mType; } - /** - * Returns whether the folder is a framework resource folder. - */ - public boolean isFramework() { - return mIsFramework; + public ResourceRepository getRepository() { + return mRepository; } /** @@ -123,15 +155,13 @@ public final class ResourceFolder extends Resource { if (mFiles != null) { for (ResourceFile file : mFiles) { - ResourceType[] types = file.getResourceTypes(); + Collection<ResourceType> types = file.getResourceTypes(); // loop through those and add them to the main list, // if they are not already present - if (types != null) { - for (ResourceType resType : types) { - if (list.indexOf(resType) == -1) { - list.add(resType); - } + for (ResourceType resType : types) { + if (list.indexOf(resType) == -1) { + list.add(resType); } } } @@ -140,11 +170,6 @@ public final class ResourceFolder extends Resource { return list; } - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.Resource#getConfiguration() - */ - @Override public FolderConfiguration getConfiguration() { return mConfiguration; } @@ -159,10 +184,10 @@ public final class ResourceFolder extends Resource { /** * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object. - * @param file The {@link IFile} object. + * @param file The {@link IAbstractFile} object. * @return the {@link ResourceFile} or null if no match was found. */ - public ResourceFile getFile(IAbstractFile file) { + private ResourceFile getFile(IAbstractFile file) { if (mFiles != null) { for (ResourceFile f : mFiles) { if (f.getFile().equals(file)) { @@ -174,27 +199,6 @@ public final class ResourceFolder extends Resource { } /** - * Returns the {@link ResourceFile} matching a {@link IFile} object. - * @param file The {@link IFile} object. - * @return the {@link ResourceFile} or null if no match was found. - */ - public ResourceFile getFile(IFile file) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - IAbstractFile abstractFile = f.getFile(); - if (abstractFile instanceof IFileWrapper) { - IFile iFile = ((IFileWrapper)f.getFile()).getIFile(); - if (iFile != null && iFile.equals(file)) { - return f; - } - } - } - } - return null; - } - - - /** * Returns the {@link ResourceFile} matching a given name. * @param filename The name of the file to return. * @return the {@link ResourceFile} or <code>null</code> if no match was found. @@ -217,7 +221,7 @@ public final class ResourceFolder extends Resource { public boolean hasResources(ResourceType type) { // Check if the folder type is able to generate resource of the type that was asked. // this is a first check to avoid going through the files. - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); + List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); boolean valid = false; for (ResourceFolderType rft : folderTypes) { @@ -239,27 +243,6 @@ public final class ResourceFolder extends Resource { return false; } - /** - * Get the list of {@link ResourceItem} of a specific type generated by all the files - * in the folder. - * This method must make sure not to create duplicates. - * @param type The type of {@link ResourceItem} to return. - * @param projectResources The global Project Resource object, allowing the implementation to - * query for already existing {@link ResourceItem} - * @return The list of <b>new</b> {@link ResourceItem} - * @see ProjectResources#findResourceItem(ResourceType, String) - */ - public Collection<ProjectResourceItem> getResources(ResourceType type, - ProjectResources projectResources) { - Collection<ProjectResourceItem> list = new ArrayList<ProjectResourceItem>(); - if (mFiles != null) { - for (ResourceFile f : mFiles) { - list.addAll(f.getResources(type, projectResources)); - } - } - return list; - } - @Override public String toString() { return mFolder.toString(); diff --git a/ide_common/src/com/android/ide/common/resources/ResourceItem.java b/ide_common/src/com/android/ide/common/resources/ResourceItem.java new file mode 100644 index 0000000..dd28a9a --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/ResourceItem.java @@ -0,0 +1,237 @@ +/* + * 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.ide.common.resources; + +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.resources.ResourceType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * An android resource. + * + * This is a representation of the resource, not of its value(s). It gives access to all + * the source files that generate this particular resource which then can be used to access + * the actual value(s). + * + * @see ResourceFile#getResources(ResourceType, ResourceRepository) + */ +public class ResourceItem implements Comparable<ResourceItem> { + + private final static Comparator<ResourceFile> sComparator = new Comparator<ResourceFile>() { + public int compare(ResourceFile file1, ResourceFile file2) { + // get both FolderConfiguration and compare them + FolderConfiguration fc1 = file1.getFolder().getConfiguration(); + FolderConfiguration fc2 = file2.getFolder().getConfiguration(); + + return fc1.compareTo(fc2); + } + }; + + private final String mName; + + /** + * List of files generating this ResourceItem. + */ + private final List<ResourceFile> mFiles = new ArrayList<ResourceFile>(); + + /** + * Constructs a new ResourceItem. + * @param name the name of the resource as it appears in the XML and R.java files. + */ + public ResourceItem(String name) { + mName = name; + } + + /** + * Returns the name of the resource. + */ + public final String getName() { + return mName; + } + + /** + * Compares the {@link ResourceItem} to another. + * @param other the ResourceItem to be compared to. + */ + public int compareTo(ResourceItem other) { + return mName.compareTo(other.mName); + } + + /** + * Returns whether the resource is editable directly. + * <p/> + * This is typically the case for resources that don't have alternate versions, or resources + * of type {@link ResourceType#ID} that aren't declared inline. + */ + public boolean isEditableDirectly() { + return hasAlternates() == false; + } + + /** + * Returns whether the ID resource has been declared inline inside another resource XML file. + * If the resource type is not {@link ResourceType#ID}, this will always return {@code false}. + */ + public boolean isDeclaredInline() { + return false; + } + + /** + * Returns a {@link ResourceValue} for this item based on the given configuration. + * If the ResourceItem has several source files, one will be selected based on the config. + * @param type the type of the resource. This is necessary because ResourceItem doesn't embed + * its type, but ResourceValue does. + * @param referenceConfig the config of the resource item. + * @param isFramework whether the resource is a framework value. Same as the type. + * @return a ResourceValue or null if none match the config. + */ + public ResourceValue getResourceValue(ResourceType type, FolderConfiguration referenceConfig, + boolean isFramework) { + // look for the best match for the given configuration + // the match has to be of type ResourceFile since that's what the input list contains + ResourceFile match = (ResourceFile) referenceConfig.findMatchingConfigurable(mFiles); + + if (match != null) { + // get the value of this configured resource. + return match.getValue(type, mName); + } + + return null; + } + + /** + * Adds a new source file. + * @param file the source file. + */ + protected void add(ResourceFile file) { + mFiles.add(file); + } + + /** + * Removes a file from the list of source files. + * @param file the file to remove + */ + protected void removeFile(ResourceFile file) { + mFiles.remove(file); + } + + /** + * Returns {@code true} if the item has no source file. + * @return + */ + protected boolean hasNoSourceFile() { + return mFiles.size() == 0; + } + + /** + * Reset the item by emptying its source file list. + */ + protected void reset() { + mFiles.clear(); + } + + /** + * Returns the sorted list of {@link ResourceItem} objects for this resource item. + */ + public ResourceFile[] getSourceFileArray() { + ArrayList<ResourceFile> list = new ArrayList<ResourceFile>(); + list.addAll(mFiles); + + Collections.sort(list, sComparator); + + return list.toArray(new ResourceFile[list.size()]); + } + + /** + * Returns the list of source file for this resource. + */ + public List<ResourceFile> getSourceFileList() { + return Collections.unmodifiableList(mFiles); + } + + /** + * Returns if the resource has at least one non-default version. + * + * @see ResourceFile#getConfiguration() + * @see FolderConfiguration#isDefault() + */ + public boolean hasAlternates() { + for (ResourceFile file : mFiles) { + if (file.getFolder().getConfiguration().isDefault() == false) { + return true; + } + } + + return false; + } + + /** + * Returns whether the resource has a default version, with no qualifier. + * + * @see ResourceFile#getConfiguration() + * @see FolderConfiguration#isDefault() + */ + public boolean hasDefault() { + for (ResourceFile file : mFiles) { + if (file.getFolder().getConfiguration().isDefault()) { + return true; + } + } + + // We only want to return false if there's no default and more than 0 items. + return (mFiles.size() == 0); + } + + /** + * Returns the number of alternate versions for this resource. + * + * @see ResourceFile#getConfiguration() + * @see FolderConfiguration#isDefault() + */ + public int getAlternateCount() { + int count = 0; + for (ResourceFile file : mFiles) { + if (file.getFolder().getConfiguration().isDefault() == false) { + count++; + } + } + + return count; + } + + /** + * Returns a formatted string usable in an XML to use for the {@link ResourceItem}. + * @param system Whether this is a system resource or a project resource. + * @return a string in the format @[type]/[name] + */ + public String getXmlString(ResourceType type, boolean system) { + if (type == ResourceType.ID && isDeclaredInline()) { + return (system ? "@android:" : "@+") + type.getName() + "/" + mName; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + return (system ? "@android:" : "@") + type.getName() + "/" + mName; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + @Override + public String toString() { + return "ResourceItem [mName=" + mName + ", mFiles=" + mFiles + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } +} diff --git a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java new file mode 100644 index 0000000..41e4f89 --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2007 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.ide.common.resources; + +import com.android.AndroidConstants; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.resources.configuration.Configurable; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.LanguageQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.io.IAbstractFolder; +import com.android.resources.FolderTypeRelationship; +import com.android.resources.ResourceFolderType; +import com.android.resources.ResourceType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * Base class for resource repository. + * + * A repository is both a file representation of a resource folder and a representation + * of the generated resources, organized by type. + * + * {@link #getResourceFolder(IAbstractFolder)} and {@link #getSourceFiles(ResourceType, String, FolderConfiguration)} + * give access to the folders and files of the resource folder. + * + * {@link #getResources(ResourceType)} gives access to the resources directly. + * + */ +public abstract class ResourceRepository { + + protected final Map<ResourceFolderType, List<ResourceFolder>> mFolderMap = + new EnumMap<ResourceFolderType, List<ResourceFolder>>(ResourceFolderType.class); + + protected final Map<ResourceType, List<ResourceItem>> mResourceMap = + new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class); + + private final Map<List<ResourceItem>, List<ResourceItem>> mReadOnlyListMap = + new IdentityHashMap<List<ResourceItem>, List<ResourceItem>>(); + + private final boolean mFrameworkRepository; + + protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null); + + + /** + * Makes a resource repository + * @param isFrameworkRepository whether the repository is for framework resources. + */ + protected ResourceRepository(boolean isFrameworkRepository) { + mFrameworkRepository = isFrameworkRepository; + } + + public boolean isFrameworkRepository() { + return mFrameworkRepository; + } + + /** + * Adds a Folder Configuration to the project. + * @param type The resource type. + * @param config The resource configuration. + * @param folder The workspace folder object. + * @return the {@link ResourceFolder} object associated to this folder. + */ + private ResourceFolder add(ResourceFolderType type, FolderConfiguration config, + IAbstractFolder folder) { + // get the list for the resource type + List<ResourceFolder> list = mFolderMap.get(type); + + if (list == null) { + list = new ArrayList<ResourceFolder>(); + + ResourceFolder cf = new ResourceFolder(type, config, folder, this); + list.add(cf); + + mFolderMap.put(type, list); + + return cf; + } + + // look for an already existing folder configuration. + for (ResourceFolder cFolder : list) { + if (cFolder.mConfiguration.equals(config)) { + // config already exist. Nothing to be done really, besides making sure + // the IAbstractFolder object is up to date. + cFolder.mFolder = folder; + return cFolder; + } + } + + // If we arrive here, this means we didn't find a matching configuration. + // So we add one. + ResourceFolder cf = new ResourceFolder(type, config, folder, this); + list.add(cf); + + return cf; + } + + /** + * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}. + * @param type The type of the folder + * @param removedFolder the IAbstractFolder object. + * @return the {@link ResourceFolder} that was removed, or null if no matches were found. + */ + public ResourceFolder removeFolder(ResourceFolderType type, IAbstractFolder removedFolder) { + // get the list of folders for the resource type. + List<ResourceFolder> list = mFolderMap.get(type); + + if (list != null) { + int count = list.size(); + for (int i = 0 ; i < count ; i++) { + ResourceFolder resFolder = list.get(i); + IAbstractFolder folder = resFolder.getFolder(); + if (removedFolder.equals(folder)) { + // we found the matching ResourceFolder. we need to remove it. + list.remove(i); + + // remove its content + resFolder.dispose(); + + return resFolder; + } + } + } + + return null; + } + + /** + * Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none + * exist, it creates one. + * + * @param type the resource type + * @param name the name of the resource. + * @return A resource item matching the type and name. + */ + protected ResourceItem getResourceItem(ResourceType type, String name) { + // looking for an existing ResourceItem with this type and name + ResourceItem item = findDeclaredResourceItem(type, name); + + // create one if there isn't one already, or if the existing one is inlined, since + // clearly we need a non inlined one (the inline one is removed too) + if (item == null || item.isDeclaredInline()) { + ResourceItem oldItem = item != null && item.isDeclaredInline() ? item : null; + + item = createResourceItem(name); + + List<ResourceItem> list = mResourceMap.get(type); + if (list == null) { + list = new ArrayList<ResourceItem>(); + mResourceMap.put(type, list); + } + + list.add(item); + + if (oldItem != null) { + list.remove(oldItem); + } + } + + return item; + } + + /** + * Creates a resource item with the given name. + * @param name the name of the resource + * @return a new ResourceItem (or child class) instance. + */ + protected abstract ResourceItem createResourceItem(String name); + + /** + * Processes a folder and adds it to the list of existing folders. + * @param folder the folder to process + * @return the ResourceFolder created from this folder, or null if the process failed. + */ + public ResourceFolder processFolder(IAbstractFolder folder) { + // split the name of the folder in segments. + String[] folderSegments = folder.getName().split(AndroidConstants.RES_QUALIFIER_SEP); + + // get the enum for the resource type. + ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]); + + if (type != null) { + // get the folder configuration. + FolderConfiguration config = FolderConfiguration.getConfig(folderSegments); + + if (config != null) { + return add(type, config, folder); + } + } + + return null; + } + + /** + * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}. + * @param type The {@link ResourceFolderType} + */ + public List<ResourceFolder> getFolders(ResourceFolderType type) { + return mFolderMap.get(type); + } + + public List<ResourceType> getAvailableResourceTypes() { + List<ResourceType> list = new ArrayList<ResourceType>(); + + // For each key, we check if there's a single ResourceType match. + // If not, we look for the actual content to give us the resource type. + + for (ResourceFolderType folderType : mFolderMap.keySet()) { + List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType); + if (types.size() == 1) { + // before we add it we check if it's not already present, since a ResourceType + // could be created from multiple folders, even for the folders that only create + // one type of resource (drawable for instance, can be created from drawable/ and + // values/) + if (list.contains(types.get(0)) == false) { + list.add(types.get(0)); + } + } else { + // there isn't a single resource type out of this folder, so we look for all + // content. + List<ResourceFolder> folders = mFolderMap.get(folderType); + if (folders != null) { + for (ResourceFolder folder : folders) { + Collection<ResourceType> folderContent = folder.getResourceTypes(); + + // then we add them, but only if they aren't already in the list. + for (ResourceType folderResType : folderContent) { + if (list.contains(folderResType) == false) { + list.add(folderResType); + } + } + } + } + } + } + + return list; + } + + /** + * Returns a list of {@link ResourceItem} matching a given {@link ResourceType}. + * @param type the type of the resource items to return + * @return a non null collection of resource items + */ + public Collection<ResourceItem> getResourceItemsOfType(ResourceType type) { + List<ResourceItem> list = mResourceMap.get(type); + + if (list == null) { + return Collections.emptyList(); + } + + List<ResourceItem> roList = mReadOnlyListMap.get(list); + if (roList == null) { + roList = Collections.unmodifiableList(list); + mReadOnlyListMap.put(list, roList); + } + + return roList; + } + + /** + * Returns whether the repository has resources of a given {@link ResourceType}. + * @param type the type of resource to check. + * @return true if the repository contains resources of the given type, false otherwise. + */ + public boolean hasResourcesOfType(ResourceType type) { + List<ResourceItem> items = mResourceMap.get(type); + return (items != null && items.size() > 0); + } + + /** + * Returns the {@link ResourceFolder} associated with a {@link IAbstractFolder}. + * @param folder The {@link IAbstractFolder} object. + * @return the {@link ResourceFolder} or null if it was not found. + */ + public ResourceFolder getResourceFolder(IAbstractFolder folder) { + for (List<ResourceFolder> list : mFolderMap.values()) { + for (ResourceFolder resFolder : list) { + IAbstractFolder wrapper = resFolder.getFolder(); + if (wrapper.equals(folder)) { + return resFolder; + } + } + } + + return null; + } + + /** + * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and + * configuration. + * <p/>This only works with files generating one resource named after the file (for instance, + * layouts, bitmap based drawable, xml, anims). + * @return the matching file or <code>null</code> if no match was found. + */ + public ResourceFile getMatchingFile(String name, ResourceFolderType type, + FolderConfiguration config) { + // get the folders for the given type + List<ResourceFolder> folders = mFolderMap.get(type); + + // look for folders containing a file with the given name. + ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>(folders.size()); + + // remove the folders that do not have a file with the given name. + for (int i = 0 ; i < folders.size(); i++) { + ResourceFolder folder = folders.get(i); + + if (folder.hasFile(name) == true) { + matchingFolders.add(folder); + } + } + + // from those, get the folder with a config matching the given reference configuration. + Configurable match = config.findMatchingConfigurable(matchingFolders); + + // do we have a matching folder? + if (match instanceof ResourceFolder) { + // get the ResourceFile from the filename + return ((ResourceFolder)match).getFile(name); + } + + return null; + } + + /** + * Returns the list of source files for a given resource. + * Optionally, if a {@link FolderConfiguration} is given, then only the best + * match for this config is returned. + * + * @param type the type of the resource. + * @param name the name of the resource. + * @param referenceConfig an optional config for which only the best match will be returned. + * + * @return a list of files generating this resource or null if it was not found. + */ + public List<ResourceFile> getSourceFiles(ResourceType type, String name, + FolderConfiguration referenceConfig) { + + Collection<ResourceItem> items = getResourceItemsOfType(type); + + for (ResourceItem item : items) { + if (name.equals(item.getName())) { + if (referenceConfig != null) { + Configurable match = referenceConfig.findMatchingConfigurable( + item.getSourceFileList()); + + if (match instanceof ResourceFile) { + return Collections.singletonList((ResourceFile) match); + } + + return null; + } + return item.getSourceFileList(); + } + } + + return null; + } + + /** + * Returns the resources values matching a given {@link FolderConfiguration}. + * + * @param referenceConfig the configuration that each value must match. + * @return a map with guaranteed to contain an entry for each {@link ResourceType} + */ + public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources( + FolderConfiguration referenceConfig) { + return doGetConfiguredResources(referenceConfig); + } + + /** + * Returns the resources values matching a given {@link FolderConfiguration} for the current + * project. + * + * @param referenceConfig the configuration that each value must match. + * @return a map with guaranteed to contain an entry for each {@link ResourceType} + */ + protected final Map<ResourceType, Map<String, ResourceValue>> doGetConfiguredResources( + FolderConfiguration referenceConfig) { + + Map<ResourceType, Map<String, ResourceValue>> map = + new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class); + + for (ResourceType key : ResourceType.values()) { + // get the local results and put them in the map + map.put(key, getConfiguredResource(key, referenceConfig)); + } + + return map; + } + + /** + * Returns the sorted list of languages used in the resources. + */ + public SortedSet<String> getLanguages() { + SortedSet<String> set = new TreeSet<String>(); + + Collection<List<ResourceFolder>> folderList = mFolderMap.values(); + for (List<ResourceFolder> folderSubList : folderList) { + for (ResourceFolder folder : folderSubList) { + FolderConfiguration config = folder.getConfiguration(); + LanguageQualifier lang = config.getLanguageQualifier(); + if (lang != null) { + set.add(lang.getShortDisplayValue()); + } + } + } + + return set; + } + + /** + * Returns the sorted list of regions used in the resources with the given language. + * @param currentLanguage the current language the region must be associated with. + */ + public SortedSet<String> getRegions(String currentLanguage) { + SortedSet<String> set = new TreeSet<String>(); + + Collection<List<ResourceFolder>> folderList = mFolderMap.values(); + for (List<ResourceFolder> folderSubList : folderList) { + for (ResourceFolder folder : folderSubList) { + FolderConfiguration config = folder.getConfiguration(); + + // get the language + LanguageQualifier lang = config.getLanguageQualifier(); + if (lang != null && lang.getShortDisplayValue().equals(currentLanguage)) { + RegionQualifier region = config.getRegionQualifier(); + if (region != null) { + set.add(region.getShortDisplayValue()); + } + } + } + } + + return set; + } + + protected void removeFile(Collection<ResourceType> types, ResourceFile file) { + for (ResourceType type : types) { + removeFile(type, file); + } + } + + protected void removeFile(ResourceType type, ResourceFile file) { + List<ResourceItem> list = mResourceMap.get(type); + for (int i = 0 ; i < list.size(); i++) { + ResourceItem item = list.get(i); + item.removeFile(file); + } + } + + /** + * Returns a map of (resource name, resource value) for the given {@link ResourceType}. + * <p/>The values returned are taken from the resource files best matching a given + * {@link FolderConfiguration}. + * @param type the type of the resources. + * @param referenceConfig the configuration to best match. + */ + private Map<String, ResourceValue> getConfiguredResource(ResourceType type, + FolderConfiguration referenceConfig) { + // get the resource item for the given type + List<ResourceItem> items = mResourceMap.get(type); + if (items == null) { + return Collections.emptyMap(); + } + + // create the map + HashMap<String, ResourceValue> map = new HashMap<String, ResourceValue>(items.size()); + + for (ResourceItem item : items) { + ResourceValue value = item.getResourceValue(type, referenceConfig, + isFrameworkRepository()); + if (value != null) { + map.put(item.getName(), value); + } + } + + return map; + } + + + /** + * Called after a resource change event, when the resource delta has been processed. + */ + protected void postUpdate() { + // Since removed files/folders remove source files from existing ResourceItem, loop through + // all resource items and remove the ones that have no source files. + + Collection<List<ResourceItem>> lists = mResourceMap.values(); + for (List<ResourceItem> list : lists) { + for (int i = 0 ; i < list.size() ;) { + if (list.get(i).hasNoSourceFile()) { + list.remove(i); + } else { + i++; + } + } + } + } + + /** + * Looks up an existing {@link ResourceItem} by {@link ResourceType} and name. This + * ignores inline resources. + * @param type the Resource Type. + * @param name the Resource name. + * @return the existing ResourceItem or null if no match was found. + */ + private ResourceItem findDeclaredResourceItem(ResourceType type, String name) { + List<ResourceItem> list = mResourceMap.get(type); + + if (list != null) { + for (ResourceItem item : list) { + // ignore inline + if (name.equals(item.getName()) && item.isDeclaredInline() == false) { + return item; + } + } + } + + return null; + } +} diff --git a/ide_common/src/com/android/ide/common/resources/ResourceResolver.java b/ide_common/src/com/android/ide/common/resources/ResourceResolver.java index 3948ac5..89a4cba 100644 --- a/ide_common/src/com/android/ide/common/resources/ResourceResolver.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceResolver.java @@ -28,13 +28,23 @@ import java.util.Map; public class ResourceResolver extends RenderResources { - private final static String REFERENCE_STYLE = ResourceType.STYLE.getName() + "/"; - private final static String PREFIX_ANDROID_RESOURCE_REF = "@android:"; - private final static String PREFIX_RESOURCE_REF = "@"; - private final static String PREFIX_ANDROID_THEME_REF = "?android:"; - private final static String PREFIX_THEME_REF = "?"; - private final static String PREFIX_ANDROID = "android:"; - + /** The constant {@code style/} */ + public final static String REFERENCE_STYLE = ResourceType.STYLE.getName() + "/"; + /** The constant {@code @android:} */ + public final static String PREFIX_ANDROID_RESOURCE_REF = "@android:"; + /** The constant {@code @} */ + public final static String PREFIX_RESOURCE_REF = "@"; + /** The constant {@code ?android:} */ + public final static String PREFIX_ANDROID_THEME_REF = "?android:"; + /** The constant {@code ?} */ + public final static String PREFIX_THEME_REF = "?"; + /** The constant {@code android:} */ + public final static String PREFIX_ANDROID = "android:"; + /** The constant {@code @style/} */ + public static final String PREFIX_STYLE = PREFIX_RESOURCE_REF + REFERENCE_STYLE; + /** The constant {@code @android:style/} */ + public static final String PREFIX_ANDROID_STYLE = PREFIX_ANDROID_RESOURCE_REF + + REFERENCE_STYLE; private final Map<ResourceType, Map<String, ResourceValue>> mProjectResources; private final Map<ResourceType, Map<String, ResourceValue>> mFrameworkResources; @@ -121,14 +131,10 @@ public class ResourceResolver extends RenderResources { if (frameworkTheme) { Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get( ResourceType.STYLE); - if (frameworkStyleMap != null) { - theme = frameworkStyleMap.get(name); - } + theme = frameworkStyleMap.get(name); } else { Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE); - if (projectStyleMap != null) { - theme = projectStyleMap.get(name); - } + theme = projectStyleMap.get(name); } if (theme instanceof StyleResourceValue) { @@ -334,29 +340,25 @@ public class ResourceResolver extends RenderResources { // if allowed, search in the project resources first. if (frameworkOnly == false) { typeMap = mProjectResources.get(resType); - if (typeMap != null) { - ResourceValue item = typeMap.get(resName); - if (item != null) { - return item; - } + ResourceValue item = typeMap.get(resName); + if (item != null) { + return item; } } // now search in the framework resources. typeMap = mFrameworkResources.get(resType); - if (typeMap != null) { - ResourceValue item = typeMap.get(resName); - if (item != null) { - return item; - } + ResourceValue item = typeMap.get(resName); + if (item != null) { + return item; + } - // if it was not found and the type is an id, it is possible that the ID was - // generated dynamically when compiling the framework resources. - // Look for it in the R map. - if (mFrameworkProvider != null && resType == ResourceType.ID) { - if (mFrameworkProvider.getId(resType, resName) != null) { - return new ResourceValue(resType, resName, true); - } + // if it was not found and the type is an id, it is possible that the ID was + // generated dynamically when compiling the framework resources. + // Look for it in the R map. + if (mFrameworkProvider != null && resType == ResourceType.ID) { + if (mFrameworkProvider.getId(resType, resName) != null) { + return new ResourceValue(resType, resName, true); } } @@ -397,32 +399,30 @@ public class ResourceResolver extends RenderResources { Map<String, ResourceValue> projectStyleMap = mProjectResources.get(ResourceType.STYLE); Map<String, ResourceValue> frameworkStyleMap = mFrameworkResources.get(ResourceType.STYLE); - if (projectStyleMap != null && frameworkStyleMap != null) { - // first, get the theme - ResourceValue theme = null; - - // project theme names have been prepended with a * - if (isProjectTheme) { - theme = projectStyleMap.get(themeName); - } else { - theme = frameworkStyleMap.get(themeName); - } - - if (theme instanceof StyleResourceValue) { - // compute the inheritance map for both the project and framework styles - computeStyleInheritance(projectStyleMap.values(), projectStyleMap, - frameworkStyleMap); + // first, get the theme + ResourceValue theme = null; - // Compute the style inheritance for the framework styles/themes. - // Since, for those, the style parent values do not contain 'android:' - // we want to force looking in the framework style only to avoid using - // similarly named styles from the project. - // To do this, we pass null in lieu of the project style map. - computeStyleInheritance(frameworkStyleMap.values(), null /*inProjectStyleMap */, - frameworkStyleMap); + // project theme names have been prepended with a * + if (isProjectTheme) { + theme = projectStyleMap.get(themeName); + } else { + theme = frameworkStyleMap.get(themeName); + } - mTheme = (StyleResourceValue) theme; - } + if (theme instanceof StyleResourceValue) { + // compute the inheritance map for both the project and framework styles + computeStyleInheritance(projectStyleMap.values(), projectStyleMap, + frameworkStyleMap); + + // Compute the style inheritance for the framework styles/themes. + // Since, for those, the style parent values do not contain 'android:' + // we want to force looking in the framework style only to avoid using + // similarly named styles from the project. + // To do this, we pass null in lieu of the project style map. + computeStyleInheritance(frameworkStyleMap.values(), null /*inProjectStyleMap */, + frameworkStyleMap); + + mTheme = (StyleResourceValue) theme; } } @@ -537,7 +537,6 @@ public class ResourceResolver extends RenderResources { null /*data*/); } - assert false; return null; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/SingleResourceFile.java b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java index 953e57a..cd2b627 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/SingleResourceFile.java +++ b/ide_common/src/com/android/ide/common/resources/SingleResourceFile.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.manager; +package com.android.ide.common.resources; import com.android.ide.common.rendering.api.DensityBasedResourceValue; import com.android.ide.common.rendering.api.ResourceValue; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; +import com.android.ide.common.resources.configuration.PixelDensityQualifier; +import com.android.io.IAbstractFile; +import com.android.resources.FolderTypeRelationship; import com.android.resources.ResourceType; -import com.android.sdklib.io.IAbstractFile; -import java.util.ArrayList; import java.util.Collection; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.List; import javax.xml.parsers.SAXParserFactory; @@ -42,16 +41,6 @@ public class SingleResourceFile extends ResourceFile { sParserFactory.setNamespaceAware(true); } - private final static Pattern sXmlPattern = Pattern.compile("^(.+)\\.xml", //$NON-NLS-1$ - Pattern.CASE_INSENSITIVE); - - private final static Pattern[] sDrawablePattern = new Pattern[] { - Pattern.compile("^(.+)\\.9\\.png", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - Pattern.compile("^(.+)\\.png", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - Pattern.compile("^(.+)\\.jpg", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - Pattern.compile("^(.+)\\.gif", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - }; - private String mResourceName; private ResourceType mType; private ResourceValue mValue; @@ -61,8 +50,8 @@ public class SingleResourceFile extends ResourceFile { // we need to infer the type of the resource from the folder type. // This is easy since this is a single Resource file. - ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(folder.getType()); - mType = types[0]; + List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folder.getType()); + mType = types.get(0); // compute the resource name mResourceName = getResourceName(mType); @@ -84,33 +73,37 @@ public class SingleResourceFile extends ResourceFile { } @Override - public ResourceType[] getResourceTypes() { - return FolderTypeRelationship.getRelatedResourceTypes(getFolder().getType()); + protected void load() { + // get a resource item matching the given type and name + ResourceItem item = getRepository().getResourceItem(mType, mResourceName); + + // add this file to the list of files generating this resource item. + item.add(this); } @Override - public boolean hasResources(ResourceType type) { - return FolderTypeRelationship.match(type, getFolder().getType()); + protected void update() { + // when this happens, nothing needs to be done since the file only generates + // a single resources that doesn't actually change (its content is the file path) } @Override - public Collection<ProjectResourceItem> getResources(ResourceType type, - ProjectResources projectResources) { - - // looking for an existing ResourceItem with this name and type - ProjectResourceItem item = projectResources.findResourceItem(type, mResourceName); + protected void dispose() { + // only remove this file from the existing ResourceItem. + getFolder().getRepository().removeFile(mType, this); - ArrayList<ProjectResourceItem> items = new ArrayList<ProjectResourceItem>(); - - if (item == null) { - item = new ConfigurableResourceItem(mResourceName); - items.add(item); - } + // don't need to touch the content, it'll get reclaimed as this objects disappear. + // In the mean time other objects may need to access it. + } - // add this ResourceFile to the ResourceItem - item.add(this); + @Override + public Collection<ResourceType> getResourceTypes() { + return FolderTypeRelationship.getRelatedResourceTypes(getFolder().getType()); + } - return items; + @Override + public boolean hasResources(ResourceType type) { + return FolderTypeRelationship.match(type, getFolder().getType()); } /* @@ -133,31 +126,11 @@ public class SingleResourceFile extends ResourceFile { // get the name from the filename. String name = getFile().getName(); - if (type == ResourceType.ANIM || - type == ResourceType.ANIMATOR || - type == ResourceType.COLOR || - type == ResourceType.INTERPOLATOR || - type == ResourceType.LAYOUT || - type == ResourceType.MENU || - type == ResourceType.XML) { - Matcher m = sXmlPattern.matcher(name); - if (m.matches()) { - return m.group(1); - } - } else if (type == ResourceType.DRAWABLE) { - for (Pattern p : sDrawablePattern) { - Matcher m = p.matcher(name); - if (m.matches()) { - return m.group(1); - } - } - - // also try the Xml pattern for selector/shape based drawable. - Matcher m = sXmlPattern.matcher(name); - if (m.matches()) { - return m.group(1); - } + int pos = name.indexOf('.'); + if (pos != -1) { + name = name.substring(0, pos); } + return name; } } diff --git a/ide_common/src/com/android/ide/common/resources/configuration/Configurable.java b/ide_common/src/com/android/ide/common/resources/configuration/Configurable.java new file mode 100644 index 0000000..5e7f910 --- /dev/null +++ b/ide_common/src/com/android/ide/common/resources/configuration/Configurable.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2007 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.ide.common.resources.configuration; + + +/** + * An object that is associated with a {@link FolderConfiguration}. + */ +public interface Configurable { + /** + * Returns the {@link FolderConfiguration} for this object. + */ + public FolderConfiguration getConfiguration(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/CountryCodeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java index fcad2dc..7195ba5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/CountryCodeQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/CountryCodeQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; - -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; +package com.android.ide.common.resources.configuration; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -98,11 +94,6 @@ public final class CountryCodeQualifier extends ResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("mcc"); //$NON-NLS-1$ - } - - @Override public boolean isValid() { return mCode != DEFAULT_CODE; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/DockModeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/DockModeQualifier.java index 37af3b4..2c832eb 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/DockModeQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/DockModeQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.DockMode; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Navigation Method. */ @@ -58,12 +55,6 @@ public final class DockModeQualifier extends EnumBasedResourceQualifier { return "Dock Mode"; } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("dockmode"); //$NON-NLS-1$ - } - @Override public boolean checkAndSet(String value, FolderConfiguration config) { DockMode mode = DockMode.getEnum(value); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/EnumBasedResourceQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java index 9526add..7bfda2d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/EnumBasedResourceQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/EnumBasedResourceQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; import com.android.resources.ResourceEnum; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java b/ide_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java index b759e6e..09cf9e4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/FolderConfiguration.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/FolderConfiguration.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,13 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; +import com.android.AndroidConstants; +import com.android.resources.ResourceFolderType; + +import java.util.ArrayList; +import java.util.List; /** @@ -24,7 +28,16 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType * value which means that the property is not set. */ public final class FolderConfiguration implements Comparable<FolderConfiguration> { - public final static String QUALIFIER_SEP = "-"; //$NON-NLS-1$ + + private final static ResourceQualifier[] DEFAULT_QUALIFIERS; + + static { + // get the default qualifiers. + FolderConfiguration defaultConfig = new FolderConfiguration(); + defaultConfig.createDefault(); + DEFAULT_QUALIFIERS = defaultConfig.getQualifiers(); + } + private final ResourceQualifier[] mQualifiers = new ResourceQualifier[INDEX_COUNT]; @@ -48,6 +61,45 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration private final static int INDEX_COUNT = 17; /** + * Creates a {@link FolderConfiguration} matching the folder segments. + * @param folderSegments The segments of the folder name. The first segments should contain + * the name of the folder + * @return a FolderConfiguration object, or null if the folder name isn't valid.. + */ + public static FolderConfiguration getConfig(String[] folderSegments) { + FolderConfiguration config = new FolderConfiguration(); + + // we are going to loop through the segments, and match them with the first + // available qualifier. If the segment doesn't match we try with the next qualifier. + // Because the order of the qualifier is fixed, we do not reset the first qualifier + // after each successful segment. + // If we run out of qualifier before processing all the segments, we fail. + + int qualifierIndex = 0; + int qualifierCount = DEFAULT_QUALIFIERS.length; + + for (int i = 1 ; i < folderSegments.length; i++) { + String seg = folderSegments[i]; + if (seg.length() > 0) { + while (qualifierIndex < qualifierCount && + DEFAULT_QUALIFIERS[qualifierIndex].checkAndSet(seg, config) == false) { + qualifierIndex++; + } + + // if we reached the end of the qualifier we didn't find a matching qualifier. + if (qualifierIndex == qualifierCount) { + return null; + } + + } else { + return null; + } + } + + return config; + } + + /** * Returns the number of {@link ResourceQualifier} that make up a Folder configuration. */ public static int getQualifierCount() { @@ -404,7 +456,7 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration if (qualifier != null) { String segment = qualifier.getFolderSegment(); if (segment != null && segment.length() > 0) { - result.append(QUALIFIER_SEP); + result.append(AndroidConstants.RES_QUALIFIER_SEP); result.append(segment); } } @@ -519,6 +571,114 @@ public final class FolderConfiguration implements Comparable<FolderConfiguration } /** + * Returns the best matching {@link Configurable} for this configuration. + * + * @param configurables the list of {@link Configurable} to choose from. + * + * @return an item from the given list of {@link Configurable} or null. + * + * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match + */ + public Configurable findMatchingConfigurable(List<? extends Configurable> configurables) { + // + // 1: eliminate resources that contradict the reference configuration + // 2: pick next qualifier type + // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4. + // 4: eliminate resources that don't use this qualifier. + // 5: if more than one resource left, go back to 2. + // + // The precedence of the qualifiers is more important than the number of qualifiers that + // exactly match the device. + + // 1: eliminate resources that contradict + ArrayList<Configurable> matchingConfigurables = new ArrayList<Configurable>(); + for (int i = 0 ; i < configurables.size(); i++) { + Configurable res = configurables.get(i); + + if (res.getConfiguration().isMatchFor(this)) { + matchingConfigurables.add(res); + } + } + + // if there is only one match, just take it + if (matchingConfigurables.size() == 1) { + return matchingConfigurables.get(0); + } else if (matchingConfigurables.size() == 0) { + return null; + } + + // 2. Loop on the qualifiers, and eliminate matches + final int count = FolderConfiguration.getQualifierCount(); + for (int q = 0 ; q < count ; q++) { + // look to see if one configurable has this qualifier. + // At the same time also record the best match value for the qualifier (if applicable). + + // The reference value, to find the best match. + // Note that this qualifier could be null. In which case any qualifier found in the + // possible match, will all be considered best match. + ResourceQualifier referenceQualifier = getQualifier(q); + + boolean found = false; + ResourceQualifier bestMatch = null; // this is to store the best match. + for (Configurable configurable : matchingConfigurables) { + ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q); + if (qualifier != null) { + // set the flag. + found = true; + + // Now check for a best match. If the reference qualifier is null , + // any qualifier is a "best" match (we don't need to record all of them. + // Instead the non compatible ones are removed below) + if (referenceQualifier != null) { + if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) { + bestMatch = qualifier; + } + } + } + } + + // 4. If a configurable has a qualifier at the current index, remove all the ones that + // do not have one, or whose qualifier value does not equal the best match found above + // unless there's no reference qualifier, in which case they are all considered + // "best" match. + if (found) { + for (int i = 0 ; i < matchingConfigurables.size(); ) { + Configurable configurable = matchingConfigurables.get(i); + ResourceQualifier qualifier = configurable.getConfiguration().getQualifier(q); + + if (qualifier == null) { + // this resources has no qualifier of this type: rejected. + matchingConfigurables.remove(configurable); + } else if (referenceQualifier != null && bestMatch != null && + bestMatch.equals(qualifier) == false) { + // there's a reference qualifier and there is a better match for it than + // this resource, so we reject it. + matchingConfigurables.remove(configurable); + } else { + // looks like we keep this resource, move on to the next one. + i++; + } + } + + // at this point we may have run out of matching resources before going + // through all the qualifiers. + if (matchingConfigurables.size() < 2) { + break; + } + } + } + + // Because we accept resources whose configuration have qualifiers where the reference + // configuration doesn't, we can end up with more than one match. In this case, we just + // take the first one. + if (matchingConfigurables.size() == 0) { + return null; + } + return matchingConfigurables.get(0); + } + + + /** * Returns whether the configuration is a match for the given reference config. * <p/>A match means that, for each qualifier of this config * <ul> diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/KeyboardStateQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java index 3cf6091..1ca5dad 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/KeyboardStateQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/KeyboardStateQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.KeyboardState; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for keyboard state. */ @@ -59,11 +56,6 @@ public final class KeyboardStateQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("keyboard"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { KeyboardState orientation = KeyboardState.getEnum(value); if (orientation != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/LanguageQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java index 2686eac..ff18bdc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/LanguageQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/LanguageQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; - -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; +package com.android.ide.common.resources.configuration; import java.util.regex.Pattern; @@ -90,11 +86,6 @@ public final class LanguageQualifier extends ResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("language"); //$NON-NLS-1$ - } - - @Override public boolean isValid() { return mValue != null; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NavigationMethodQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java index 5faa293..6c7e31f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NavigationMethodQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/NavigationMethodQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.Navigation; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Navigation Method. */ @@ -58,12 +55,6 @@ public final class NavigationMethodQualifier extends EnumBasedResourceQualifier return NAME; } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("navpad"); //$NON-NLS-1$ - } - @Override public boolean checkAndSet(String value, FolderConfiguration config) { Navigation method = Navigation.getEnum(value); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NavigationStateQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java index 8cea2d3..9b1e07e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NavigationStateQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/NavigationStateQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.NavigationState; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for navigation state. */ @@ -59,11 +56,6 @@ public final class NavigationStateQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("navpad"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { NavigationState state = NavigationState.getEnum(value); if (state != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NetworkCodeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java index 4ef0c75..295e8ab 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NetworkCodeQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/NetworkCodeQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; - -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; +package com.android.ide.common.resources.configuration; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -98,11 +94,6 @@ public final class NetworkCodeQualifier extends ResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("mnc"); //$NON-NLS-1$ - } - - @Override public boolean isValid() { return mCode != DEFAULT_CODE; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NightModeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java index 15aea6b..9e49091 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/NightModeQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/NightModeQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.NightMode; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Navigation Method. */ @@ -59,11 +56,6 @@ public final class NightModeQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("nightmode"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { NightMode mode = NightMode.getEnum(value); if (mode != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/PixelDensityQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/PixelDensityQualifier.java index 32fc0c5..80842a8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/PixelDensityQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/PixelDensityQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.Density; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -63,11 +60,6 @@ public final class PixelDensityQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("dpi"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { Density density = Density.getEnum(value); if (density == null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/RegionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java index dfe02cf..7e8ca9a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/RegionQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/RegionQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; - -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; +package com.android.ide.common.resources.configuration; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -94,11 +90,6 @@ public final class RegionQualifier extends ResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("region"); //$NON-NLS-1$ - } - - @Override public boolean isValid() { return mValue != null; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ResourceQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java index b4d9a34..6abac4e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ResourceQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/ResourceQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import org.eclipse.swt.graphics.Image; /** * Base class for resource qualifiers. @@ -36,11 +35,6 @@ public abstract class ResourceQualifier implements Comparable<ResourceQualifier> public abstract String getShortName(); /** - * Returns the icon for the qualifier. - */ - public abstract Image getIcon(); - - /** * Returns whether the qualifier has a valid filter value. */ public abstract boolean isValid(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenDimensionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java index c9ff7c2..a58789a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenDimensionQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; - -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; +package com.android.ide.common.resources.configuration; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -75,11 +71,6 @@ public final class ScreenDimensionQualifier extends ResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("dimension"); //$NON-NLS-1$ - } - - @Override public boolean isValid() { return mValue1 != DEFAULT_SIZE && mValue2 != DEFAULT_SIZE; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenOrientationQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java index d7d6bd3..c26a6f4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenOrientationQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.ResourceEnum; import com.android.resources.ScreenOrientation; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Screen Orientation. */ @@ -58,11 +55,6 @@ public final class ScreenOrientationQualifier extends EnumBasedResourceQualifier } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("orientation"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { ScreenOrientation orientation = ScreenOrientation.getEnum(value); if (orientation != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenRatioQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java index 4444273..4cbf0a4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenRatioQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenRatioQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2009 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.ResourceEnum; import com.android.resources.ScreenRatio; -import org.eclipse.swt.graphics.Image; - public class ScreenRatioQualifier extends EnumBasedResourceQualifier { public static final String NAME = "Screen Ratio"; @@ -55,11 +52,6 @@ public class ScreenRatioQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("ratio"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { ScreenRatio size = ScreenRatio.getEnum(value); if (size != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenSizeQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java index 023a861..7ab6dd8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/ScreenSizeQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/ScreenSizeQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2009 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.ResourceEnum; import com.android.resources.ScreenSize; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Screen Size. Size can be "small", "normal", "large" and "x-large" */ @@ -59,11 +56,6 @@ public class ScreenSizeQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("size"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { ScreenSize size = ScreenSize.getEnum(value); if (size != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/TextInputMethodQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java index b5ce166..3d772aa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/TextInputMethodQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/TextInputMethodQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.Keyboard; import com.android.resources.ResourceEnum; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Text Input Method. */ @@ -60,11 +57,6 @@ public final class TextInputMethodQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("text_input"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { Keyboard method = Keyboard.getEnum(value); if (method != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/TouchScreenQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java index f3b8eb0..eeb68d2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/TouchScreenQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/TouchScreenQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.resources.ResourceEnum; import com.android.resources.TouchScreen; -import org.eclipse.swt.graphics.Image; - /** * Resource Qualifier for Touch Screen type. @@ -60,11 +57,6 @@ public final class TouchScreenQualifier extends EnumBasedResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("touch"); //$NON-NLS-1$ - } - - @Override public boolean checkAndSet(String value, FolderConfiguration config) { TouchScreen type = TouchScreen.getEnum(value); if (type != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/VersionQualifier.java b/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java index 199e804..c7cef97 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/configurations/VersionQualifier.java +++ b/ide_common/src/com/android/ide/common/resources/configuration/VersionQualifier.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2009 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,11 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.resources.configurations; - -import com.android.ide.eclipse.adt.internal.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; +package com.android.ide.common.resources.configuration; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -99,11 +95,6 @@ public final class VersionQualifier extends ResourceQualifier { } @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("version"); //$NON-NLS-1$ - } - - @Override public boolean isValid() { return mVersion != DEFAULT_VERSION; } diff --git a/ide_common/tests/.classpath b/ide_common/tests/.classpath new file mode 100644 index 0000000..3cc56e3 --- /dev/null +++ b/ide_common/tests/.classpath @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry combineaccessrules="false" kind="src" path="/ide_common"/> + <classpathentry combineaccessrules="false" kind="src" path="/common"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/ide_common/tests/.project b/ide_common/tests/.project new file mode 100644 index 0000000..1cfa60e --- /dev/null +++ b/ide_common/tests/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>ide_common-tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/draw9patch/src/Android.mk b/ide_common/tests/Android.mk index 3dc9db4..9443dba 100644 --- a/draw9patch/src/Android.mk +++ b/ide_common/tests/Android.mk @@ -1,4 +1,4 @@ -# Copyright (C) 2008 The Android Open Source Project +# 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. @@ -13,14 +13,15 @@ # limitations under the License. LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := resources +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_MODULE := ide_common-tests +LOCAL_MODULE_TAGS := optional -LOCAL_JAR_MANIFEST := ../etc/manifest.txt -LOCAL_JAVA_LIBRARIES := \ - swing-worker-1.1 -LOCAL_MODULE := draw9patch +LOCAL_JAVA_LIBRARIES := common ide_common junit include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/CountryCodeQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/CountryCodeQualifierTest.java index e0fe013..eba8b8d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/CountryCodeQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/CountryCodeQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; - -import com.android.ide.eclipse.adt.internal.resources.configurations.CountryCodeQualifier; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; +package com.android.ide.common.resources.configuration; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/DockModeQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/DockModeQualifierTest.java index de05f7e..195d474 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/DockModeQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/DockModeQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.DockModeQualifier; import com.android.resources.DockMode; import junit.framework.TestCase; diff --git a/ide_common/tests/src/com/android/ide/common/resources/configuration/FolderConfigurationTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/FolderConfigurationTest.java new file mode 100644 index 0000000..6d0d487 --- /dev/null +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/FolderConfigurationTest.java @@ -0,0 +1,38 @@ +/* + * 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.ide.common.resources.configuration; + +import junit.framework.TestCase; + +public class FolderConfigurationTest extends TestCase { + + /* + * Test createDefault creates all the qualifiers. + */ + public void testCreateDefault() { + FolderConfiguration defaultConfig = new FolderConfiguration(); + defaultConfig.createDefault(); + + // this is always valid and up to date. + final int count = FolderConfiguration.getQualifierCount(); + + // make sure all the qualifiers were created. + for (int i = 0 ; i < count ; i++) { + assertNotNull(defaultConfig.getQualifier(i)); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/KeyboardStateQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/KeyboardStateQualifierTest.java index b97d1f8..cf52a38 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/KeyboardStateQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/KeyboardStateQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.KeyboardStateQualifier; import com.android.resources.KeyboardState; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/LanguageQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/LanguageQualifierTest.java index e4a9312..2dfd65f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/LanguageQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/LanguageQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,15 +14,12 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; - -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier; +package com.android.ide.common.resources.configuration; import junit.framework.TestCase; public class LanguageQualifierTest extends TestCase { - + private FolderConfiguration config; private LanguageQualifier lq; @@ -39,14 +36,14 @@ public class LanguageQualifierTest extends TestCase { config = null; lq = null; } - + public void testCheckAndSet() { assertEquals(true, lq.checkAndSet("en", config)); //$NON-NLS-1$ assertTrue(config.getLanguageQualifier() != null); assertEquals("en", config.getLanguageQualifier().toString()); //$NON-NLS-1$ - + } - + public void testFailures() { assertEquals(false, lq.checkAndSet("", config)); //$NON-NLS-1$ assertEquals(false, lq.checkAndSet("EN", config)); //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/NavigationMethodQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/NavigationMethodQualifierTest.java index ebc34ad..4237dde 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/NavigationMethodQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/NavigationMethodQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.NavigationMethodQualifier; import com.android.resources.Navigation; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/NetworkCodeQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java index 7512305..6896316 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/NetworkCodeQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/NetworkCodeQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2008 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; - -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.NetworkCodeQualifier; +package com.android.ide.common.resources.configuration; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/PixelDensityQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/PixelDensityQualifierTest.java index 747e6d7..b99f2af 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/PixelDensityQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/PixelDensityQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.PixelDensityQualifier; import com.android.resources.Density; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/RegionQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/RegionQualifierTest.java index a70a04b..fc0402c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/RegionQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/RegionQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; - -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier; +package com.android.ide.common.resources.configuration; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/ScreenDimensionQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifierTest.java index b25fb76..e57424f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/ScreenDimensionQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/ScreenDimensionQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,7 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; - -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenDimensionQualifier; +package com.android.ide.common.resources.configuration; import junit.framework.TestCase; @@ -39,7 +36,7 @@ public class ScreenDimensionQualifierTest extends TestCase { sdq = null; config = null; } - + public void testCheckAndSet() { assertEquals(true, sdq.checkAndSet("400x200", config));//$NON-NLS-1$ assertTrue(config.getScreenDimensionQualifier() != null); @@ -47,7 +44,7 @@ public class ScreenDimensionQualifierTest extends TestCase { assertEquals(200, config.getScreenDimensionQualifier().getValue2()); assertEquals("400x200", config.getScreenDimensionQualifier().toString()); //$NON-NLS-1$ } - + public void testFailures() { assertEquals(false, sdq.checkAndSet("", config));//$NON-NLS-1$ assertEquals(false, sdq.checkAndSet("400X200", config));//$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/ScreenOrientationQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifierTest.java index b960c97..3aac5f3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/ScreenOrientationQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/ScreenOrientationQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenOrientationQualifier; import com.android.resources.ScreenOrientation; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/ScreenSizeQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java index 26cf1f3..b19f125 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/ScreenSizeQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/ScreenSizeQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2010 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; import com.android.resources.ScreenSize; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/TextInputMethodQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/TextInputMethodQualifierTest.java index f9ba80b..bc2c890 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/TextInputMethodQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/TextInputMethodQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.TextInputMethodQualifier; import com.android.resources.Keyboard; import junit.framework.TestCase; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/TouchScreenQualifierTest.java b/ide_common/tests/src/com/android/ide/common/resources/configuration/TouchScreenQualifierTest.java index 1dafa8b..04f8a30 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/resources/configurations/TouchScreenQualifierTest.java +++ b/ide_common/tests/src/com/android/ide/common/resources/configuration/TouchScreenQualifierTest.java @@ -1,11 +1,11 @@ /* * Copyright (C) 2007 The Android Open Source Project * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.eclipse.org/org/documents/epl-v10.php + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,10 +14,8 @@ * limitations under the License. */ -package com.android.ide.eclipse.adt.internal.editors.resources.configurations; +package com.android.ide.common.resources.configuration; -import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.adt.internal.resources.configurations.TouchScreenQualifier; import com.android.resources.TouchScreen; import junit.framework.TestCase; diff --git a/layoutlib_api/.settings/org.eclipse.jdt.core.prefs b/layoutlib_api/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..1cb4685 --- /dev/null +++ b/layoutlib_api/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 09 14:02:32 PST 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/layoutlib_api/NOTICE b/layoutlib_api/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/layoutlib_api/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/AdapterBinding.java b/layoutlib_api/src/com/android/ide/common/rendering/api/AdapterBinding.java new file mode 100644 index 0000000..9481246 --- /dev/null +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/AdapterBinding.java @@ -0,0 +1,81 @@ +/* + * 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.ide.common.rendering.api; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Describe the content of the dynamic android.widget.Adapter used to fill + * android.widget.AdapterView + */ +public class AdapterBinding implements Iterable<DataBindingItem> { + + private final int mRepeatCount; + private final List<ResourceReference> mHeaders = new ArrayList<ResourceReference>(); + private final List<DataBindingItem> mItems = new ArrayList<DataBindingItem>(); + private final List<ResourceReference> mFooters = new ArrayList<ResourceReference>(); + + public AdapterBinding(int repeatCount) { + mRepeatCount = repeatCount; + } + + public int getRepeatCount() { + return mRepeatCount; + } + + public void addHeader(ResourceReference layoutInfo) { + mHeaders.add(layoutInfo); + } + + public int getHeaderCount() { + return mHeaders.size(); + } + + public ResourceReference getHeaderAt(int index) { + return mHeaders.get(index); + } + + public void addItem(DataBindingItem item) { + mItems.add(item); + } + + public int getItemCount() { + return mItems.size(); + } + + public DataBindingItem getItemAt(int index) { + return mItems.get(index); + } + + public void addFooter(ResourceReference layoutInfo) { + mFooters.add(layoutInfo); + } + + public int getFooterCount() { + return mFooters.size(); + } + + public ResourceReference getFooterAt(int index) { + return mFooters.get(index); + } + + public Iterator<DataBindingItem> iterator() { + return mItems.iterator(); + } +} diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java b/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java index 48309cf..c044353 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/Bridge.java @@ -17,6 +17,8 @@ package com.android.ide.common.rendering.api; +import static com.android.ide.common.rendering.api.Result.Status.NOT_IMPLEMENTED; + import com.android.ide.common.rendering.api.Result.Status; import java.awt.image.BufferedImage; @@ -109,4 +111,40 @@ public abstract class Bridge { public void clearCaches(Object projectKey) { } + + /** + * Utility method returning the parent of a given view object. + * + * @param viewObject the object for which to return the parent. + * + * @return a {@link Result} indicating the status of the action, and if success, the parent + * object in {@link Result#getData()} + */ + public Result getViewParent(Object viewObject) { + return NOT_IMPLEMENTED.createResult(); + } + + /** + * Utility method returning the index of a given view in its parent. + * @param viewObject the object for which to return the index. + * + * @return a {@link Result} indicating the status of the action, and if success, the index in + * the parent in {@link Result#getData()} + */ + public Result getViewIndex(Object viewObject) { + return NOT_IMPLEMENTED.createResult(); + } + + /** + * Utility method returning the baseline value for a given view object. This basically returns + * View.getBaseline(). + * + * @param viewObject the object for which to return the index. + * + * @return the baseline value or -1 if not applicable to the view object or if this layout + * library does not implement this method. + */ + public int getViewBaseline(Object viewObject) { + return -1; + } } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java b/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java index ff6777b..6620571 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/Capability.java @@ -24,38 +24,41 @@ public enum Capability { /** Ability to render at full size, as required by the layout, and unbound by the screen */ UNBOUND_RENDERING, /** Ability to override the background of the rendering with transparency using - * {@link SceneParams#setCustomBackgroundColor(int)} */ + * {@link SessionParams#setOverrideBgColor(int)} */ CUSTOM_BACKGROUND_COLOR, - /** Ability to call {@link LayoutScene#render()} and {@link LayoutScene#render(long)}. */ + /** Ability to call {@link RenderSession#render()} and {@link RenderSession#render(long)}. */ RENDER, + /** Ability to ask for a layout only with no rendering through + * {@link SessionParams#setLayoutOnly()} + */ + LAYOUT_ONLY, /** - * Ability to control embedded layout parsers through {@link IXmlPullParser#getParser(String)} + * Ability to control embedded layout parsers through {@link ILayoutPullParser#getParser(String)} */ EMBEDDED_LAYOUT, /** Ability to call<br> - * {@link LayoutScene#insertChild(Object, IXmlPullParser, int, com.android.layoutlib.api.LayoutScene.IAnimationListener)}<br> - * {@link LayoutScene#moveChild(Object, Object, int, java.util.Map, com.android.layoutlib.api.LayoutScene.IAnimationListener)}<br> - * {@link LayoutScene#removeChild(Object, com.android.layoutlib.api.LayoutScene.IAnimationListener)}<br> - * {@link LayoutScene#setProperty(Object, String, String)}<br> + * {@link RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)}<br> + * {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}<br> + * {@link RenderSession#setProperty(Object, String, String)}<br> * The method that receives an animation listener can only use it if the * ANIMATED_VIEW_MANIPULATION, or FULL_ANIMATED_VIEW_MANIPULATION is also supported. - * * */ VIEW_MANIPULATION, /** Ability to play animations with<br> - * {@link LayoutScene#animate(Object, String, boolean, com.android.layoutlib.api.LayoutScene.IAnimationListener)} + * {@link RenderSession#animate(Object, String, boolean, IAnimationListener)} */ PLAY_ANIMATION, /** * Ability to manipulate views with animation, as long as the view does not change parent. - * {@link LayoutScene#insertChild(Object, IXmlPullParser, int, com.android.layoutlib.api.LayoutScene.IAnimationListener)}<br> - * {@link LayoutScene#moveChild(Object, Object, int, java.util.Map, com.android.layoutlib.api.LayoutScene.IAnimationListener)}<br> - * {@link LayoutScene#removeChild(Object, com.android.layoutlib.api.LayoutScene.IAnimationListener)}<br> + * {@link RenderSession#insertChild(Object, ILayoutPullParser, int, IAnimationListener)}<br> + * {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)}<br> + * {@link RenderSession#removeChild(Object, IAnimationListener)}<br> */ ANIMATED_VIEW_MANIPULATION, /** * Ability to move views (even into a different ViewGroup) with animation. - * see {@link LayoutScene#moveChild(Object, Object, int, java.util.Map, com.android.layoutlib.api.LayoutScene.IAnimationListener)} + * see {@link RenderSession#moveChild(Object, Object, int, java.util.Map, IAnimationListener)} */ - FULL_ANIMATED_VIEW_MANIPULATION; + FULL_ANIMATED_VIEW_MANIPULATION, + ADAPTER_BINDING; } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/DataBindingItem.java b/layoutlib_api/src/com/android/ide/common/rendering/api/DataBindingItem.java new file mode 100644 index 0000000..93569bd --- /dev/null +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/DataBindingItem.java @@ -0,0 +1,96 @@ +/* + * 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.ide.common.rendering.api; + +import com.android.resources.ResourceType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * A data binding item. It contain a {@link ResourceReference} to the view used to represent it. + * It also contains how many items of this type the AdapterView should display. + * + * It can also contain an optional list of children in case the AdapterView is an + * ExpandableListView. In this case, the count value is used as a repeat count for the children, + * similar to {@link AdapterBinding#getRepeatCount()}. + * + */ +public class DataBindingItem implements Iterable<DataBindingItem> { + private final ResourceReference mReference; + private final int mCount; + private List<DataBindingItem> mChildren; + + public DataBindingItem(ResourceReference reference, int count) { + mReference = reference; + mCount = count; + } + + public DataBindingItem(String name, boolean platformLayout, int count) { + this(new ResourceReference(name, platformLayout), count); + } + + public DataBindingItem(String name, boolean platformLayout) { + this(name, platformLayout, 1); + } + + public DataBindingItem(String name, int count) { + this(name, false /*platformLayout*/, count); + } + + public DataBindingItem(String name) { + this(name, false /*platformLayout*/, 1); + } + + /** + * Returns the {@link ResourceReference} for the view. The {@link ResourceType} for the + * referenced resource is implied to be {@link ResourceType#LAYOUT}. + */ + public ResourceReference getViewReference() { + return mReference; + } + + /** + * The repeat count for this object or the repeat count for the children if there are any. + */ + public int getCount() { + return mCount; + } + + public void addChild(DataBindingItem child) { + if (mChildren == null) { + mChildren = new ArrayList<DataBindingItem>(); + } + + mChildren.add(child); + } + + public List<DataBindingItem> getChildren() { + if (mChildren != null) { + return mChildren; + } + + return Collections.emptyList(); + } + + public Iterator<DataBindingItem> iterator() { + List<DataBindingItem> list = getChildren(); + return list.iterator(); + } +} diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/DensityBasedResourceValue.java b/layoutlib_api/src/com/android/ide/common/rendering/api/DensityBasedResourceValue.java index ca60640..f63f16f 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/DensityBasedResourceValue.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/DensityBasedResourceValue.java @@ -41,6 +41,7 @@ public class DensityBasedResourceValue extends ResourceValue implements IDensity /** Legacy method, do not call * @deprecated use {@link #getResourceDensity()} instead. */ + @Deprecated public Density getDensity() { return Density.getEnum(mDensity.getDpiValue()); } @@ -51,4 +52,35 @@ public class DensityBasedResourceValue extends ResourceValue implements IDensity + getResourceType() + "/" + getName() + " = " + getValue() + " (density:" + mDensity +", framework:" + isFramework() + ")]"; } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((mDensity == null) ? 0 : mDensity.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + DensityBasedResourceValue other = (DensityBasedResourceValue) obj; + if (mDensity == null) { + if (other.mDensity != null) + return false; + } else if (!mDensity.equals(other.mDensity)) + return false; + return true; + } } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/ILayoutPullParser.java b/layoutlib_api/src/com/android/ide/common/rendering/api/ILayoutPullParser.java index 4b033d9..574f9bb 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/ILayoutPullParser.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/ILayoutPullParser.java @@ -38,8 +38,9 @@ public interface ILayoutPullParser extends XmlPullParser { * @param layoutName the name of the layout. * @return returns a custom parser or null if no custom parsers are needed. * - * @since 5 + * @deprecated use {@link IProjectCallback#getParser(String)} instead */ + @Deprecated ILayoutPullParser getParser(String layoutName); } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/IProjectCallback.java b/layoutlib_api/src/com/android/ide/common/rendering/api/IProjectCallback.java index 0ec214f..b91b598 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/IProjectCallback.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/IProjectCallback.java @@ -19,6 +19,8 @@ package com.android.ide.common.rendering.api; import com.android.resources.ResourceType; import com.android.util.Pair; +import java.net.URL; + /** * Callback for project information needed by the Layout Library. * Classes implementing this interface provide methods giving access to some project data, like @@ -26,6 +28,23 @@ import com.android.util.Pair; */ public interface IProjectCallback { + public enum ViewAttribute { + TEXT(String.class), + IS_CHECKED(Boolean.class), + SRC(URL.class), + COLOR(Integer.class); + + private final Class<?> mClass; + + private ViewAttribute(Class<?> theClass) { + mClass = theClass; + } + + public Class<?> getAttributeClass() { + return mClass; + } + } + /** * Loads a custom view with the given constructor signature and arguments. * @param name The fully qualified name of the class. @@ -73,4 +92,52 @@ public interface IProjectCallback { * @return an Integer containing the resource Id, or <code>null</code> if not found. */ Integer getResourceId(ResourceType type, String name); + + /** + * Returns a custom parser for the layout of the given name. + * @param layoutName the name of the layout. + * @return returns a custom parser or null if no custom parsers are needed. + */ + ILayoutPullParser getParser(String layoutName); + + /** + * Returns the value of an item used by an adapter. + * @param adapterView The {@link ResourceReference} for the adapter view info. + * @param adapterCookie the view cookie for this particular view. + * @param itemRef the {@link ResourceReference} for the layout used by the adapter item. + * @param fullPosition the position of the item in the full list. + * @param positionPerType the position of the item if only items of the same type are + * considered. If there is only one type of items, this is the same as + * <var>fullPosition</var>. + * @param fullParentPosition the full position of the item's parent. This is only + * valid if the adapter view is an ExpandableListView. + * @param parentPositionPerType the position of the parent's item, only considering items + * of the same type. This is only valid if the adapter view is an ExpandableListView. + * If there is only one type of items, this is the same as <var>fullParentPosition</var>. + * @param viewRef The {@link ResourceReference} for the view we're trying to fill. + * @param ViewAttribute the attribute being queried. + * @param defaultValue the default value for this attribute. The object class matches the + * class associated with the {@link ViewAttribute}. + * @return the item value or null if there's no value. + * + * @see ViewAttribute#getAttributeClass() + */ + Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, + ResourceReference itemRef, + int fullPosition, int positionPerType, + int fullParentPosition, int parentPositionPerType, + ResourceReference viewRef, ViewAttribute viewAttribute, Object defaultValue); + + /** + * Returns an adapter binding for a given adapter view. + * This is only called if {@link SessionParams} does not have an {@link AdapterBinding} for + * the given {@link ResourceReference} already. + * + * @param adapterViewRef the reference of adapter view to return the adapter binding for. + * @param adapterCookie the view cookie for this particular view. + * @param viewObject the view object for the adapter. + * @return an adapter binding for the given view or null if there's no data. + */ + AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie, + Object viewObject); } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/RenderSession.java b/layoutlib_api/src/com/android/ide/common/rendering/api/RenderSession.java index a2e087c..188909e 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/RenderSession.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/RenderSession.java @@ -162,29 +162,6 @@ public class RenderSession { } /** - * Returns the View parent. - * - * @param viewObject the object for which to return the parent. - * - * @return a {@link Result} indicating the status of the action, and if success, the parent - * object in {@link Result#getData()} - */ - public Result getViewParent(Object viewObject) { - return NOT_IMPLEMENTED.createResult(); - } - - /** - * Returns the index of a given view it its parent. - * @param viewObject the object for which to return the index. - * - * @return a {@link Result} indicating the status of the action, and if success, the index in - * the parent in {@link Result#getData()} - */ - public Result getViewIndex(Object viewObject) { - return NOT_IMPLEMENTED.createResult(); - } - - /** * Inserts a new child in a ViewGroup object, and renders the result. * <p/> * The child is first inflated and then added to its new parent, at the given <var>index<var> diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceReference.java b/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceReference.java new file mode 100644 index 0000000..f22f51e --- /dev/null +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceReference.java @@ -0,0 +1,103 @@ +/* + * 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.ide.common.rendering.api; + +/** + * A resource reference. This contains the String ID of the resource and whether this is a framework + * reference. + * This is an immutable class. + * + */ +public class ResourceReference { + private final String mName; + private final boolean mIsFramework; + + /** + * Builds a resource reference. + * @param name the name of the resource + * @param isFramework whether the reference is to a framework resource. + */ + public ResourceReference(String name, boolean isFramework) { + mName = name; + mIsFramework = isFramework; + } + + /** + * Builds a non-framework resource reference. + * @param name the name of the resource + */ + public ResourceReference(String name) { + this(name, false /*platformLayout*/); + } + + /** + * Returns the name of the resource, as defined in the XML. + */ + public final String getName() { + return mName; + } + + /** + * Returns whether the resource is a framework resource (<code>true</code>) or a project + * resource (<code>false</false>). + */ + public final boolean isFramework() { + return mIsFramework; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (mIsFramework ? 1231 : 1237); + result = prime * result + ((mName == null) ? 0 : mName.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ResourceReference other = (ResourceReference) obj; + if (mIsFramework != other.mIsFramework) + return false; + if (mName == null) { + if (other.mName != null) + return false; + } else if (!mName.equals(other.mName)) + return false; + return true; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ResourceReference [" + mName + " (framework:" + mIsFramework+ ")]"; + } +} diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceValue.java b/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceValue.java index f15d903..bb7dab4 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceValue.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/ResourceValue.java @@ -23,23 +23,19 @@ import com.android.resources.ResourceType; * Represents an android resource with a name and a string value. */ @SuppressWarnings("deprecation") -public class ResourceValue implements IResourceValue { +public class ResourceValue extends ResourceReference implements IResourceValue { private final ResourceType mType; - private final String mName; private String mValue = null; - private final boolean mIsFramwork; - public ResourceValue(ResourceType type, String name, boolean isFramwork) { + public ResourceValue(ResourceType type, String name, boolean isFramework) { + super(name, isFramework); mType = type; - mName = name; - mIsFramwork = isFramwork; } public ResourceValue(ResourceType type, String name, String value, boolean isFramework) { + super(name, isFramework); mType = type; - mName = name; mValue = value; - mIsFramwork = isFramework; } public ResourceType getResourceType() { @@ -50,18 +46,12 @@ public class ResourceValue implements IResourceValue { * Returns the type of the resource. For instance "drawable", "color", etc... * @deprecated use {@link #getResourceType()} instead. */ + @Deprecated public String getType() { return mType.getName(); } /** - * Returns the name of the resource, as defined in the XML. - */ - public final String getName() { - return mName; - } - - /** * Returns the value of the resource, as defined in the XML. This can be <code>null</code> */ public final String getValue() { @@ -69,14 +59,6 @@ public class ResourceValue implements IResourceValue { } /** - * Returns whether the resource is a framework resource (<code>true</code>) or a project - * resource (<code>false</false>). - */ - public final boolean isFramework() { - return mIsFramwork; - } - - /** * Sets the value of the resource. * @param value the new value */ @@ -94,9 +76,44 @@ public class ResourceValue implements IResourceValue { @Override public String toString() { - return "ResourceValue [" + mType + "/" + mName + " = " + mValue - + " (framework:" + mIsFramwork + ")]"; + return "ResourceValue [" + mType + "/" + getName() + " = " + mValue //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + " (framework:" + isFramework() + ")]"; //$NON-NLS-1$ //$NON-NLS-2$ } + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((mType == null) ? 0 : mType.hashCode()); + result = prime * result + ((mValue == null) ? 0 : mValue.hashCode()); + return result; + } + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + ResourceValue other = (ResourceValue) obj; + if (mType == null) { + if (other.mType != null) + return false; + } else if (!mType.equals(other.mType)) + return false; + if (mValue == null) { + if (other.mValue != null) + return false; + } else if (!mValue.equals(other.mValue)) + return false; + return true; + } } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java b/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java index 9446ff5..f4f6b5c 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/SessionParams.java @@ -18,9 +18,12 @@ package com.android.ide.common.rendering.api; import com.android.resources.Density; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + /** * Rendering parameters for a {@link RenderSession}. - * */ public class SessionParams extends RenderParams { @@ -47,69 +50,94 @@ public class SessionParams extends RenderParams { } } - private final ILayoutPullParser mLayoutDescription; private final RenderingMode mRenderingMode; + private boolean mLayoutOnly = false; + private Map<ResourceReference, AdapterBinding> mAdapterBindingMap; /** - * - * @param layoutDescription the {@link ILayoutPullParser} letting the LayoutLib Bridge visit the - * layout file. - * @param renderingMode The rendering mode. - * @param projectKey An Object identifying the project. This is used for the cache mechanism. - * @param screenWidth the screen width - * @param screenHeight the screen height - * @param density the density factor for the screen. - * @param xdpi the screen actual dpi in X - * @param ydpi the screen actual dpi in Y - * @param themeName The name of the theme to use. - * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme. - * @param projectResources the resources of the project. The map contains (String, map) pairs - * where the string is the type of the resource reference used in the layout file, and the - * map contains (String, {@link ResourceValue}) pairs where the key is the resource name, - * and the value is the resource value. - * @param frameworkResources the framework resources. The map contains (String, map) pairs - * where the string is the type of the resource reference used in the layout file, and the map - * contains (String, {@link ResourceValue}) pairs where the key is the resource name, and the - * value is the resource value. - * @param projectCallback The {@link IProjectCallback} object to get information from - * the project. - * @param minSdkVersion the minSdkVersion of the project - * @param targetSdkVersion the targetSdkVersion of the project - * @param log the object responsible for displaying warning/errors to the user. - */ - public SessionParams( - ILayoutPullParser layoutDescription, - RenderingMode renderingMode, - Object projectKey, - int screenWidth, int screenHeight, - Density density, float xdpi, float ydpi, - RenderResources renderResources, - IProjectCallback projectCallback, - int minSdkVersion, int targetSdkVersion, - LayoutLog log) { - super(projectKey, screenWidth, screenHeight, density, xdpi, ydpi, - renderResources, projectCallback, minSdkVersion, targetSdkVersion, log); - - mLayoutDescription = layoutDescription; - mRenderingMode = renderingMode; - - } - - public SessionParams(SessionParams params) { - super(params); - mLayoutDescription = params.mLayoutDescription; - mRenderingMode = params.mRenderingMode; - } - - public ILayoutPullParser getLayoutDescription() { - return mLayoutDescription; - } - - public RenderingMode getRenderingMode() { - return mRenderingMode; - } + * + * @param layoutDescription the {@link ILayoutPullParser} letting the LayoutLib Bridge visit the + * layout file. + * @param renderingMode The rendering mode. + * @param projectKey An Object identifying the project. This is used for the cache mechanism. + * @param screenWidth the screen width + * @param screenHeight the screen height + * @param density the density factor for the screen. + * @param xdpi the screen actual dpi in X + * @param ydpi the screen actual dpi in Y + * @param themeName The name of the theme to use. + * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme. + * @param projectResources the resources of the project. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the + * map contains (String, {@link ResourceValue}) pairs where the key is the resource name, + * and the value is the resource value. + * @param frameworkResources the framework resources. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the map + * contains (String, {@link ResourceValue}) pairs where the key is the resource name, and the + * value is the resource value. + * @param projectCallback The {@link IProjectCallback} object to get information from + * the project. + * @param minSdkVersion the minSdkVersion of the project + * @param targetSdkVersion the targetSdkVersion of the project + * @param log the object responsible for displaying warning/errors to the user. + */ + public SessionParams( + ILayoutPullParser layoutDescription, + RenderingMode renderingMode, + Object projectKey, + int screenWidth, int screenHeight, + Density density, float xdpi, float ydpi, + RenderResources renderResources, + IProjectCallback projectCallback, + int minSdkVersion, int targetSdkVersion, + LayoutLog log) { + super(projectKey, screenWidth, screenHeight, density, xdpi, ydpi, + renderResources, projectCallback, minSdkVersion, targetSdkVersion, log); + + mLayoutDescription = layoutDescription; + mRenderingMode = renderingMode; + } + + public SessionParams(SessionParams params) { + super(params); + mLayoutDescription = params.mLayoutDescription; + mRenderingMode = params.mRenderingMode; + if (params.mAdapterBindingMap != null) { + mAdapterBindingMap = new HashMap<ResourceReference, AdapterBinding>( + params.mAdapterBindingMap); + } + } + + public ILayoutPullParser getLayoutDescription() { + return mLayoutDescription; + } + + public RenderingMode getRenderingMode() { + return mRenderingMode; + } + + public void setLayoutOnly() { + mLayoutOnly = true; + } + + public boolean isLayoutOnly() { + return mLayoutOnly; + } + public void addAdapterBinding(ResourceReference reference, AdapterBinding data) { + if (mAdapterBindingMap == null) { + mAdapterBindingMap = new HashMap<ResourceReference, AdapterBinding>(); + } + mAdapterBindingMap.put(reference, data); + } + public Map<ResourceReference, AdapterBinding> getAdapterBindings() { + if (mAdapterBindingMap == null) { + return Collections.emptyMap(); + } + + return Collections.unmodifiableMap(mAdapterBindingMap); + } } diff --git a/layoutlib_api/src/com/android/ide/common/rendering/api/StyleResourceValue.java b/layoutlib_api/src/com/android/ide/common/rendering/api/StyleResourceValue.java index 429bd26..9d1e65d 100644 --- a/layoutlib_api/src/com/android/ide/common/rendering/api/StyleResourceValue.java +++ b/layoutlib_api/src/com/android/ide/common/rendering/api/StyleResourceValue.java @@ -75,8 +75,8 @@ public final class StyleResourceValue extends ResourceValue implements IStyleRes * Legacy method. * @deprecated use {@link #getValue()} */ + @Deprecated public IResourceValue findItem(String name) { return mItems.get(name); } - } diff --git a/layoutopt/NOTICE b/layoutopt/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/layoutopt/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/monkeyrunner/etc/monkeyrunner b/monkeyrunner/etc/monkeyrunner index 364be2a..fe9be0c 100755 --- a/monkeyrunner/etc/monkeyrunner +++ b/monkeyrunner/etc/monkeyrunner @@ -69,6 +69,29 @@ else jarpath="$frameworkdir/$jarfile" fi +# Figure out the path to the swt.jar for the current architecture. +# if ANDROID_SWT is defined, then just use this. +# else, if running in the Android source tree, then look for the correct swt folder in prebuilt +# else, look for the correct swt folder in the SDK under tools/lib/ +swtpath="" +if [ -n "$ANDROID_SWT" ]; then + swtpath="$ANDROID_SWT" +else + vmarch=`java -jar "${frameworkdir}"/archquery.jar` + if [ -n "$ANDROID_BUILD_TOP" ]; then + osname=`uname -s | tr A-Z a-z` + swtpath="${ANDROID_BUILD_TOP}/prebuilt/${osname}-${vmarch}/swt" + else + swtpath="${frameworkdir}/${vmarch}" + fi +fi + +if [ ! -d "$swtpath" ]; then + echo "SWT folder '${swtpath}' does not exist." + echo "Please export ANDROID_SWT to point to the folder containing swt.jar for your platform." + exit 1 +fi + # need to use "java.ext.dirs" because "-jar" causes classpath to be ignored # might need more memory, e.g. -Xmx128M -exec java -Xmx128M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir" -Djava.library.path="$libdir" -Dcom.android.monkeyrunner.bindir="$progdir" -jar "$jarpath" "$@" +exec java -Xmx128M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir:$swtpath" -Djava.library.path="$libdir" -Dcom.android.monkeyrunner.bindir="$progdir" -jar "$jarpath" "$@" diff --git a/monkeyrunner/etc/monkeyrunner.bat b/monkeyrunner/etc/monkeyrunner.bat index 1cf38ca..5028b3f 100644 --- a/monkeyrunner/etc/monkeyrunner.bat +++ b/monkeyrunner/etc/monkeyrunner.bat @@ -43,4 +43,21 @@ if exist %frameworkdir%%jarfile% goto JarFileOk set jarpath=%frameworkdir%%jarfile% -call %java_exe% -Xmx512m -Djava.ext.dirs=%frameworkdir% -Dcom.android.monkeyrunner.bindir=..\framework -jar %jarpath% %* +if not defined ANDROID_SWT goto QueryArch + set swt_path=%ANDROID_SWT% + goto SwtDone + +:QueryArch + + for /f %%a in ('%java_exe% -jar %frameworkdir%archquery.jar') do set swt_path=%frameworkdir%%%a + +:SwtDone + +if exist %swt_path% goto SetPath + echo SWT folder '%swt_path%' does not exist. + echo Please set ANDROID_SWT to point to the folder containing swt.jar for your platform. + exit /B + +:SetPath + +call %java_exe% -Xmx512m -Djava.ext.dirs=%frameworkdir%;%swt_path% -Dcom.android.monkeyrunner.bindir=..\framework -jar %jarpath% %* diff --git a/monkeyrunner/src/Android.mk b/monkeyrunner/src/Android.mk index 38be272..8cca0bc 100644 --- a/monkeyrunner/src/Android.mk +++ b/monkeyrunner/src/Android.mk @@ -24,7 +24,9 @@ LOCAL_JAVA_LIBRARIES := \ jython \ guavalib \ jsilver \ - sdklib + sdklib \ + hierarchyviewerlib \ + swt LOCAL_JAVA_RESOURCE_DIRS := resources diff --git a/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java b/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java index 864441e..badddff 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java +++ b/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java @@ -23,15 +23,18 @@ import java.text.BreakIterator; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.python.core.ArgParser; import org.python.core.ClassDictInit; import org.python.core.Py; +import org.python.core.PyBoolean; import org.python.core.PyDictionary; import org.python.core.PyFloat; import org.python.core.PyInteger; @@ -73,6 +76,7 @@ public final class JythonUtils { // What python calls float, most people call double builder.put(PyFloat.class, Double.class); builder.put(PyInteger.class, Integer.class); + builder.put(PyBoolean.class, Boolean.class); PYOBJECT_TO_JAVA_OBJECT_MAP = builder.build(); } @@ -228,6 +232,8 @@ public final class JythonUtils { } else if (o instanceof Float) { float f = (Float) o; return new PyFloat(f); + } else if (o instanceof Boolean) { + return new PyBoolean((Boolean) o); } return Py.None; } @@ -479,4 +485,20 @@ public final class JythonUtils { lines.add(currentLine.toString()); return lines; } + + /** + * Obtain the set of method names available from Python. + * + * @param clazz Class to inspect. + * @return set of method names annotated with {@code MonkeyRunnerExported}. + */ + public static Set<String> getMethodNames(Class<?> clazz) { + HashSet<String> methodNames = new HashSet<String>(); + for (Method m: clazz.getMethods()) { + if (m.isAnnotationPresent(MonkeyRunnerExported.class)) { + methodNames.add(m.getName()); + } + } + return methodNames; + } } diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java index 0f4362a..649e33c 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java +++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java @@ -20,8 +20,6 @@ import java.util.Collections; import java.util.Map; import java.util.Set; -import javax.annotation.Nullable; - import org.python.core.ArgParser; import org.python.core.ClassDictInit; import org.python.core.Py; @@ -30,7 +28,11 @@ import org.python.core.PyException; import org.python.core.PyObject; import org.python.core.PyTuple; +import com.android.monkeyrunner.core.IMonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyImage; import com.android.monkeyrunner.doc.MonkeyRunnerExported; +import com.android.monkeyrunner.easy.HierarchyViewer; + import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.collect.Collections2; @@ -43,7 +45,7 @@ import com.google.common.collect.ImmutableMap; * implementation of this class. */ @MonkeyRunnerExported(doc = "Represents a device attached to the system.") -public abstract class MonkeyDevice extends PyObject implements ClassDictInit { +public class MonkeyDevice extends PyObject implements ClassDictInit { public static void classDictInit(PyObject dict) { JythonUtils.convertDocAnnotationsForClass(MonkeyDevice.class, dict); } @@ -55,37 +57,41 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { @MonkeyRunnerExported(doc = "Sends a DOWN event, immediately followed by an UP event when used with touch() or press()") public static final String DOWN_AND_UP = "downAndUp"; + // TODO: This may not be accessible from jython; if so, remove it. public enum TouchPressType { DOWN, UP, DOWN_AND_UP, } - public static final Map<String, TouchPressType> TOUCH_NAME_TO_ENUM = - ImmutableMap.of(MonkeyDevice.DOWN, TouchPressType.DOWN, - MonkeyDevice.UP, TouchPressType.UP, - MonkeyDevice.DOWN_AND_UP, TouchPressType.DOWN_AND_UP); + public static final Map<String, IMonkeyDevice.TouchPressType> TOUCH_NAME_TO_ENUM = + ImmutableMap.of(DOWN, IMonkeyDevice.TouchPressType.DOWN, + UP, IMonkeyDevice.TouchPressType.UP, + DOWN_AND_UP, IMonkeyDevice.TouchPressType.DOWN_AND_UP); private static final Set<String> VALID_DOWN_UP_TYPES = TOUCH_NAME_TO_ENUM.keySet(); - /** - * Create a MonkeyMananger for talking to this device. - * - * NOTE: This is not part of the jython API. - * - * @return the MonkeyManager - */ - public abstract MonkeyManager getManager(); - - /** - * Dispose of any native resoureces this device may have taken hold of. - * - * NOTE: This is not part of the jython API. - */ - public abstract void dispose(); + private IMonkeyDevice impl; + + public MonkeyDevice(IMonkeyDevice impl) { + this.impl = impl; + } + + public IMonkeyDevice getImpl() { + return impl; + } + + @MonkeyRunnerExported(doc = "Get the HierarchyViewer object for the device.", + returns = "A HierarchyViewer object") + public HierarchyViewer getHierarchyViewer(PyObject[] args, String[] kws) { + return impl.getHierarchyViewer(); + } @MonkeyRunnerExported(doc = "Gets the device's screen buffer, yielding a screen capture of the entire display.", returns = "A MonkeyImage object (a bitmap wrapper)") - public abstract MonkeyImage takeSnapshot(); + public MonkeyImage takeSnapshot() { + IMonkeyImage image = impl.takeSnapshot(); + return new MonkeyImage(image); + } @MonkeyRunnerExported(doc = "Given the name of a variable on the device, " + "returns the variable's value", @@ -97,7 +103,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { ArgParser ap = JythonUtils.createArgParser(args, kws); Preconditions.checkNotNull(ap); - return getProperty(ap.getString(0)); + return impl.getProperty(ap.getString(0)); } @MonkeyRunnerExported(doc = "Synonym for getProperty()", @@ -107,7 +113,8 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { public String getSystemProperty(PyObject[] args, String[] kws) { ArgParser ap = JythonUtils.createArgParser(args, kws); Preconditions.checkNotNull(ap); - return getSystemProperty(ap.getString(0)); + + return impl.getSystemProperty(ap.getString(0)); } @MonkeyRunnerExported(doc = "Sends a touch event at the specified location", @@ -136,7 +143,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { // bad stuff was passed in, just use the already specified default value type = MonkeyDevice.DOWN_AND_UP; } - touch(x, y, TOUCH_NAME_TO_ENUM.get(type)); + impl.touch(x, y, TOUCH_NAME_TO_ENUM.get(type)); } @MonkeyRunnerExported(doc = "Simulates dragging (touch, hold, and move) on the device screen.", @@ -171,7 +178,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { int steps = ap.getInt(3, 10); - drag(startx, starty, endx, endy, steps, ms); + impl.drag(startx, starty, endx, endy, steps, ms); } @MonkeyRunnerExported(doc = "Send a key event to the specified key", @@ -199,7 +206,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { // bad stuff was passed in, just use the already specified default value type = MonkeyDevice.DOWN_AND_UP; } - press(name, TOUCH_NAME_TO_ENUM.get(type)); + impl.press(name, TOUCH_NAME_TO_ENUM.get(type)); } @MonkeyRunnerExported(doc = "Types the specified string on the keyboard. This is " + @@ -211,7 +218,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { Preconditions.checkNotNull(ap); String message = ap.getString(0); - type(message); + impl.type(message); } @MonkeyRunnerExported(doc = "Executes an adb shell command and returns the result, if any.", @@ -223,7 +230,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { Preconditions.checkNotNull(ap); String cmd = ap.getString(0); - return shell(cmd); + return impl.shell(cmd); } @MonkeyRunnerExported(doc = "Reboots the specified device into a specified bootloader.", @@ -235,7 +242,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { String into = ap.getString(0, null); - reboot(into); + impl.reboot(into); } @MonkeyRunnerExported(doc = "Installs the specified Android package (.apk file) " + @@ -248,7 +255,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { Preconditions.checkNotNull(ap); String path = ap.getString(0); - return installPackage(path); + return impl.installPackage(path); } @MonkeyRunnerExported(doc = "Deletes the specified package from the device, including its " + @@ -261,7 +268,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { Preconditions.checkNotNull(ap); String packageName = ap.getString(0); - return removePackage(packageName); + return impl.removePackage(packageName); } @MonkeyRunnerExported(doc = "Starts an Activity on the device by sending an Intent " + @@ -294,7 +301,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { String component = ap.getString(6, null); int flags = ap.getInt(7, 0); - startActivity(uri, action, data, mimetype, categories, extras, component, flags); + impl.startActivity(uri, action, data, mimetype, categories, extras, component, flags); } @MonkeyRunnerExported(doc = "Sends a broadcast intent to the device.", @@ -326,7 +333,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { String component = ap.getString(6, null); int flags = ap.getInt(7, 0); - broadcastIntent(uri, action, data, mimetype, categories, extras, component, flags); + impl.broadcastIntent(uri, action, data, mimetype, categories, extras, component, flags); } @MonkeyRunnerExported(doc = "Run the specified package with instrumentation and return " + @@ -354,7 +361,7 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { instrumentArgs = Collections.emptyMap(); } - Map<String, Object> result = instrument(packageName, instrumentArgs); + Map<String, Object> result = impl.instrument(packageName, instrumentArgs); return JythonUtils.convertMapToDict(result); } @@ -363,34 +370,6 @@ public abstract class MonkeyDevice extends PyObject implements ClassDictInit { ArgParser ap = JythonUtils.createArgParser(args, kws); Preconditions.checkNotNull(ap); - wake(); + impl.wake(); } - - /** - * Reboot the device. - * - * @param into which bootloader to boot into. Null means default reboot. - */ - public abstract void reboot(@Nullable String into); - - public abstract String getProperty(String key); - public abstract String getSystemProperty(String key); - public abstract void touch(int x, int y, TouchPressType type); - public abstract void press(String keyName, TouchPressType type); - public abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms); - public abstract void type(String string); - public abstract String shell(String cmd); - public abstract boolean installPackage(String path); - public abstract boolean removePackage(String packageName); - public abstract void startActivity(@Nullable String uri, @Nullable String action, - @Nullable String data, @Nullable String mimetype, - Collection<String> categories, Map<String, Object> extras, @Nullable String component, - int flags); - public abstract void broadcastIntent(@Nullable String uri, @Nullable String action, - @Nullable String data, @Nullable String mimetype, - Collection<String> categories, Map<String, Object> extras, @Nullable String component, - int flags); - public abstract Map<String, Object> instrument(String packageName, - Map<String, Object> args); - public abstract void wake(); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java index d506613..b55b4f3 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java +++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java @@ -17,6 +17,7 @@ package com.android.monkeyrunner; import com.google.common.base.Preconditions; +import com.android.monkeyrunner.core.IMonkeyImage; import com.android.monkeyrunner.doc.MonkeyRunnerExported; import org.python.core.ArgParser; @@ -25,57 +26,31 @@ import org.python.core.PyInteger; import org.python.core.PyObject; import org.python.core.PyTuple; -import java.awt.Graphics; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.Iterator; - -import javax.imageio.ImageIO; -import javax.imageio.ImageWriter; -import javax.imageio.stream.ImageOutputStream; +import java.util.logging.Logger; /** * Jython object to encapsulate images that have been taken. */ @MonkeyRunnerExported(doc = "An image") -public abstract class MonkeyImage extends PyObject implements ClassDictInit { +public class MonkeyImage extends PyObject implements ClassDictInit { + private static Logger LOG = Logger.getLogger(MonkeyImage.class.getCanonicalName()); + public static void classDictInit(PyObject dict) { JythonUtils.convertDocAnnotationsForClass(MonkeyImage.class, dict); } - /** - * Convert the MonkeyImage into a BufferedImage. - * - * @return a BufferedImage for this MonkeyImage. - */ - public abstract BufferedImage createBufferedImage(); + private IMonkeyImage impl; - // Cache the BufferedImage so we don't have to generate it every time. - private WeakReference<BufferedImage> cachedBufferedImage = null; - - /** - * Utility method to handle getting the BufferedImage and managing the cache. - * - * @return the BufferedImage for this image. - */ - private BufferedImage getBufferedImage() { - // Check the cache first - if (cachedBufferedImage != null) { - BufferedImage img = cachedBufferedImage.get(); - if (img != null) { - return img; - } - } + public MonkeyImage(IMonkeyImage impl) { + this.impl = impl; + } - // Not in the cache, so create it and cache it. - BufferedImage img = createBufferedImage(); - cachedBufferedImage = new WeakReference<BufferedImage>(img); - return img; + public IMonkeyImage getImpl() { + return impl; } + @MonkeyRunnerExported(doc = "Converts the MonkeyImage into a particular format and returns " + "the result as a String. Use this to get access to the raw" + "pixels in a particular format. String output is for better " + @@ -89,16 +64,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit { Preconditions.checkNotNull(ap); String format = ap.getString(0, "png"); - - BufferedImage argb = convertSnapshot(); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - try { - ImageIO.write(argb, format, os); - } catch (IOException e) { - return new byte[0]; - } - return os.toByteArray(); + return impl.convertToBytes(format); } @MonkeyRunnerExported(doc = "Write the MonkeyImage to a file. If no " + @@ -116,38 +82,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit { String path = ap.getString(0); String format = ap.getString(1, null); - - if (format != null) { - return writeToFile(path, format); - } - int offset = path.lastIndexOf('.'); - if (offset < 0) { - return writeToFile(path, "png"); - } - String ext = path.substring(offset + 1); - Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(ext); - if (!writers.hasNext()) { - return writeToFile(path, "png"); - } - ImageWriter writer = writers.next(); - BufferedImage image = getBufferedImage(); - try { - File f = new File(path); - f.delete(); - - ImageOutputStream outputStream = ImageIO.createImageOutputStream(f); - writer.setOutput(outputStream); - - try { - writer.write(image); - } finally { - writer.dispose(); - outputStream.flush(); - } - } catch (IOException e) { - return false; - } - return true; + return impl.writeToFile(path, format); } @MonkeyRunnerExported(doc = "Get a single ARGB (alpha, red, green, blue) pixel at location " + @@ -163,7 +98,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit { int x = ap.getInt(0); int y = ap.getInt(1); - int pixel = getPixel(x, y); + int pixel = impl.getPixel(x, y); PyInteger a = new PyInteger((pixel & 0xFF000000) >> 24); PyInteger r = new PyInteger((pixel & 0x00FF0000) >> 16); PyInteger g = new PyInteger((pixel & 0x0000FF00) >> 8); @@ -184,35 +119,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit { int x = ap.getInt(0); int y = ap.getInt(1); - return getPixel(x, y); - } - - private int getPixel(int x, int y) { - BufferedImage image = getBufferedImage(); - return image.getRGB(x, y); - } - - private BufferedImage convertSnapshot() { - BufferedImage image = getBufferedImage(); - - // Convert the image to ARGB so ImageIO writes it out nicely - BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(), - BufferedImage.TYPE_INT_ARGB); - Graphics g = argb.createGraphics(); - g.drawImage(image, 0, 0, null); - g.dispose(); - return argb; - } - - public boolean writeToFile(String path, String format) { - BufferedImage argb = convertSnapshot(); - - try { - ImageIO.write(argb, format, new File(path)); - } catch (IOException e) { - return false; - } - return true; + return impl.getPixel(x, y); } @MonkeyRunnerExported(doc = "Compare this MonkeyImage object to aother MonkeyImage object.", @@ -227,53 +134,13 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit { Preconditions.checkNotNull(ap); PyObject otherObject = ap.getPyObject(0); - MonkeyImage other = (MonkeyImage) otherObject.__tojava__(MonkeyImage.class); + // TODO: check if this conversion wortks + IMonkeyImage other = (IMonkeyImage) otherObject.__tojava__( + IMonkeyImage.class); double percent = JythonUtils.getFloat(ap, 1, 1.0); - BufferedImage otherImage = other.getBufferedImage(); - BufferedImage myImage = getBufferedImage(); - - // Easy size check - if (otherImage.getWidth() != myImage.getWidth()) { - return false; - } - if (otherImage.getHeight() != myImage.getHeight()) { - return false; - } - - int[] otherPixel = new int[1]; - int[] myPixel = new int[1]; - - int width = myImage.getWidth(); - int height = myImage.getHeight(); - - int numDiffPixels = 0; - // Now, go through pixel-by-pixel and check that the images are the same; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) { - numDiffPixels++; - } - } - } - double numberPixels = (height * width); - double diffPercent = numDiffPixels / numberPixels; - return percent <= 1.0 - diffPercent; - } - - private static class BufferedImageMonkeyImage extends MonkeyImage { - private final BufferedImage image; - - public BufferedImageMonkeyImage(BufferedImage image) { - this.image = image; - } - - @Override - public BufferedImage createBufferedImage() { - return image; - } - + return impl.sameAs(other, percent); } @MonkeyRunnerExported(doc = "Copy a rectangular region of the image.", @@ -292,7 +159,7 @@ public abstract class MonkeyImage extends PyObject implements ClassDictInit { int w = rect.__getitem__(2).asInt(); int h = rect.__getitem__(3).asInt(); - BufferedImage image = getBufferedImage(); - return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h)); + IMonkeyImage image = impl.getSubImage(x, y, w, h); + return new MonkeyImage(image); } -} +}
\ No newline at end of file diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java index 8480223..5529802 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java +++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunner.java @@ -19,6 +19,10 @@ import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.collect.Collections2; +import com.android.monkeyrunner.core.IMonkeyBackend; +import com.android.monkeyrunner.core.IMonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyImage; +import com.android.monkeyrunner.core.MonkeyImageBase; import com.android.monkeyrunner.doc.MonkeyRunnerExported; import org.python.core.ArgParser; @@ -38,7 +42,7 @@ import javax.swing.JOptionPane; @MonkeyRunnerExported(doc = "Main entry point for MonkeyRunner") public class MonkeyRunner extends PyObject implements ClassDictInit { private static final Logger LOG = Logger.getLogger(MonkeyRunner.class.getCanonicalName()); - private static MonkeyRunnerBackend backend; + private static IMonkeyBackend backend; public static void classDictInit(PyObject dict) { JythonUtils.convertDocAnnotationsForClass(MonkeyRunner.class, dict); @@ -49,7 +53,7 @@ public class MonkeyRunner extends PyObject implements ClassDictInit { * * @param backend the backend to use. */ - /* package */ static void setBackend(MonkeyRunnerBackend backend) { + /* package */ static void setBackend(IMonkeyBackend backend) { MonkeyRunner.backend = backend; } @@ -71,8 +75,10 @@ public class MonkeyRunner extends PyObject implements ClassDictInit { timeoutMs = Long.MAX_VALUE; } - return backend.waitForConnection(timeoutMs, + IMonkeyDevice device = backend.waitForConnection(timeoutMs, ap.getString(1, ".*")); + MonkeyDevice monkeyDevice = new MonkeyDevice(device); + return monkeyDevice; } @MonkeyRunnerExported(doc = "Pause the currently running program for the specified " + @@ -174,6 +180,21 @@ public class MonkeyRunner extends PyObject implements ClassDictInit { return choice(message, title, choices); } + @MonkeyRunnerExported(doc = "Loads a MonkeyImage from a file.", + args = { "path" }, + argDocs = { + "The path to the file to load. This file path is in terms of the computer running " + + "MonkeyRunner and not a path on the Android Device. " }, + returns = "A new MonkeyImage representing the specified file") + public static MonkeyImage loadImageFromFile(PyObject[] args, String kws[]) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + String path = ap.getString(0); + IMonkeyImage image = MonkeyImageBase.loadImageFromFile(path); + return new MonkeyImage(image); + } + /** * Display an alert dialog. * diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java index 90fce6f..8c9942c 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java +++ b/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java @@ -20,6 +20,7 @@ import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.android.monkeyrunner.adb.AdbBackend; +import com.android.monkeyrunner.core.IMonkeyBackend; import com.android.monkeyrunner.stub.StubBackend; import org.python.util.PythonInterpreter; @@ -50,7 +51,7 @@ public class MonkeyRunnerStarter { private static final Logger LOG = Logger.getLogger(MonkeyRunnerStarter.class.getName()); private static final String MONKEY_RUNNER_MAIN_MANIFEST_NAME = "MonkeyRunnerStartupRunner"; - private final MonkeyRunnerBackend backend; + private final IMonkeyBackend backend; private final MonkeyRunnerOptions options; public MonkeyRunnerStarter(MonkeyRunnerOptions options) { @@ -68,7 +69,7 @@ public class MonkeyRunnerStarter { * @param backendName the name of the backend to create * @return the new backend, or null if none were found. */ - public static MonkeyRunnerBackend createBackendByName(String backendName) { + public static IMonkeyBackend createBackendByName(String backendName) { if ("adb".equals(backendName)) { return new AdbBackend(); } else if ("stub".equals(backendName)) { @@ -191,13 +192,12 @@ public class MonkeyRunnerStarter { public static void main(String[] args) { MonkeyRunnerOptions options = MonkeyRunnerOptions.processOptions(args); - // logging property files are difficult - replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE, options.getLogLevel()); - if (options == null) { return; } + // logging property files are difficult + replaceAllLogFormatters(MonkeyFormatter.DEFAULT_INSTANCE, options.getLogLevel()); MonkeyRunnerStarter runner = new MonkeyRunnerStarter(options); int error = runner.run(); diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java index 455d131..49cac08 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java +++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbBackend.java @@ -19,8 +19,8 @@ import com.google.common.collect.Lists; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.IDevice; -import com.android.monkeyrunner.MonkeyDevice; -import com.android.monkeyrunner.MonkeyRunnerBackend; +import com.android.monkeyrunner.core.IMonkeyBackend; +import com.android.monkeyrunner.core.IMonkeyDevice; import com.android.sdklib.SdkConstants; import java.io.File; @@ -32,12 +32,11 @@ import java.util.regex.Pattern; /** * Backend implementation that works over ADB to talk to the device. */ -public class AdbBackend implements MonkeyRunnerBackend { +public class AdbBackend implements IMonkeyBackend { private static Logger LOG = Logger.getLogger(AdbBackend.class.getCanonicalName()); // How long to wait each time we check for the device to be connected. private static final int CONNECTION_ITERATION_TIMEOUT_MS = 200; - private final List<AdbMonkeyDevice> devices = Lists.newArrayList(); - + private final List<IMonkeyDevice> devices = Lists.newArrayList(); private final AndroidDebugBridge bridge; public AdbBackend() { @@ -87,18 +86,20 @@ public class AdbBackend implements MonkeyRunnerBackend { return null; } - public MonkeyDevice waitForConnection() { + @Override + public IMonkeyDevice waitForConnection() { return waitForConnection(Integer.MAX_VALUE, ".*"); } - public MonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex) { + @Override + public IMonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex) { do { IDevice device = findAttacedDevice(deviceIdRegex); // Only return the device when it is online if (device != null && device.getState() == IDevice.DeviceState.ONLINE) { - AdbMonkeyDevice amd = new AdbMonkeyDevice(device); - devices.add(amd); - return amd; + IMonkeyDevice monkeyDevice = new AdbMonkeyDevice(device); + devices.add(monkeyDevice); + return monkeyDevice; } try { @@ -113,8 +114,9 @@ public class AdbBackend implements MonkeyRunnerBackend { return null; } + @Override public void shutdown() { - for (AdbMonkeyDevice device : devices) { + for (IMonkeyDevice device : devices) { device.dispose(); } AndroidDebugBridge.terminate(); diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java index e7e2e1c..60eaba9 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java +++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java @@ -25,10 +25,11 @@ import com.android.ddmlib.IDevice; import com.android.ddmlib.InstallException; import com.android.ddmlib.ShellCommandUnresponsiveException; import com.android.ddmlib.TimeoutException; -import com.android.monkeyrunner.MonkeyDevice; -import com.android.monkeyrunner.MonkeyImage; import com.android.monkeyrunner.MonkeyManager; import com.android.monkeyrunner.adb.LinearInterpolator.Point; +import com.android.monkeyrunner.core.IMonkeyImage; +import com.android.monkeyrunner.core.IMonkeyDevice; +import com.android.monkeyrunner.easy.HierarchyViewer; import java.io.IOException; import java.net.InetAddress; @@ -47,7 +48,7 @@ import java.util.regex.Pattern; import javax.annotation.Nullable; -public class AdbMonkeyDevice extends MonkeyDevice { +public class AdbMonkeyDevice implements IMonkeyDevice { private static final Logger LOG = Logger.getLogger(AdbMonkeyDevice.class.getName()); private static final String[] ZERO_LENGTH_STRING_ARRAY = new String[0]; @@ -81,9 +82,15 @@ public class AdbMonkeyDevice extends MonkeyDevice { manager = null; } + @Override + public HierarchyViewer getHierarchyViewer() { + return new HierarchyViewer(device); + } + private void executeAsyncCommand(final String command, final LoggingOutputReceiver logger) { executor.submit(new Runnable() { + @Override public void run() { try { device.executeShellCommand(command, logger); @@ -184,7 +191,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { } @Override - public MonkeyImage takeSnapshot() { + public IMonkeyImage takeSnapshot() { try { return new AdbMonkeyImage(device.getScreenshot()); } catch (TimeoutException e) { @@ -419,9 +426,10 @@ public class AdbMonkeyDevice extends MonkeyDevice { } else { // treat is as a string. valueString = value.toString(); - arg = "--esmake"; + arg = "--es"; } parts.add(arg); + parts.add(entry.getKey()); parts.add(valueString); } @@ -498,6 +506,7 @@ public class AdbMonkeyDevice extends MonkeyDevice { LinearInterpolator.Point start = new LinearInterpolator.Point(startx, starty); LinearInterpolator.Point end = new LinearInterpolator.Point(endx, endy); lerp.interpolate(start, end, new LinearInterpolator.Callback() { + @Override public void step(Point point) { try { manager.touchMove(point.getX(), point.getY()); @@ -512,9 +521,11 @@ public class AdbMonkeyDevice extends MonkeyDevice { } } + @Override public void start(Point point) { try { manager.touchDown(point.getX(), point.getY()); + manager.touchMove(point.getX(), point.getY()); } catch (IOException e) { LOG.log(Level.SEVERE, "Error sending drag start event", e); } @@ -526,8 +537,10 @@ public class AdbMonkeyDevice extends MonkeyDevice { } } + @Override public void end(Point point) { try { + manager.touchMove(point.getX(), point.getY()); manager.touchUp(point.getX(), point.getY()); } catch (IOException e) { LOG.log(Level.SEVERE, "Error sending drag end event", e); diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java index fc32600..e2bd86e 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java +++ b/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyImage.java @@ -16,15 +16,15 @@ package com.android.monkeyrunner.adb; import com.android.ddmlib.RawImage; -import com.android.monkeyrunner.MonkeyImage; import com.android.monkeyrunner.adb.image.ImageUtils; +import com.android.monkeyrunner.core.MonkeyImageBase; import java.awt.image.BufferedImage; /** * ADB implementation of the MonkeyImage class. */ -public class AdbMonkeyImage extends MonkeyImage { +public class AdbMonkeyImage extends MonkeyImageBase { private final RawImage image; /** diff --git a/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java b/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java index 7e31ea5..5a317f1 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java +++ b/monkeyrunner/src/com/android/monkeyrunner/adb/image/CaptureRawAndConvertedImage.java @@ -16,9 +16,11 @@ package com.android.monkeyrunner.adb.image; import com.android.ddmlib.RawImage; -import com.android.monkeyrunner.MonkeyDevice; import com.android.monkeyrunner.adb.AdbBackend; import com.android.monkeyrunner.adb.AdbMonkeyImage; +import com.android.monkeyrunner.core.IMonkeyBackend; +import com.android.monkeyrunner.core.IMonkeyImage; +import com.android.monkeyrunner.core.IMonkeyDevice; import java.io.FileOutputStream; import java.io.IOException; @@ -94,13 +96,13 @@ public class CaptureRawAndConvertedImage { } public static void main(String[] args) throws IOException { - AdbBackend backend = new AdbBackend(); - MonkeyDevice device = backend.waitForConnection(); - AdbMonkeyImage snapshot = (AdbMonkeyImage) device.takeSnapshot(); + IMonkeyBackend backend = new AdbBackend(); + IMonkeyDevice device = backend.waitForConnection(); + IMonkeyImage snapshot = (IMonkeyImage) device.takeSnapshot(); // write out to a file snapshot.writeToFile("output.png", "png"); - writeOutImage(snapshot.getRawImage(), "output.raw"); + writeOutImage(((AdbMonkeyImage)snapshot).getRawImage(), "output.raw"); System.exit(0); } } diff --git a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java index e199a75..ca3195c 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java +++ b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyController.java @@ -15,8 +15,9 @@ */ package com.android.monkeyrunner.controller; -import com.android.monkeyrunner.MonkeyDevice; import com.android.monkeyrunner.adb.AdbBackend; +import com.android.monkeyrunner.core.IMonkeyBackend; +import com.android.monkeyrunner.core.IMonkeyDevice; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -36,9 +37,10 @@ public class MonkeyController extends JFrame { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { - AdbBackend adb = new AdbBackend(); - final MonkeyDevice device = adb.waitForConnection(); + IMonkeyBackend adb = new AdbBackend(); + final IMonkeyDevice device = adb.waitForConnection(); MonkeyControllerFrame mf = new MonkeyControllerFrame(device); mf.setVisible(true); mf.addWindowListener(new WindowAdapter() { diff --git a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java index 7f5a7d8..7750936 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java +++ b/monkeyrunner/src/com/android/monkeyrunner/controller/MonkeyControllerFrame.java @@ -15,10 +15,10 @@ */ package com.android.monkeyrunner.controller; -import com.android.monkeyrunner.MonkeyDevice; -import com.android.monkeyrunner.MonkeyImage; import com.android.monkeyrunner.MonkeyManager; import com.android.monkeyrunner.PhysicalButton; +import com.android.monkeyrunner.core.IMonkeyImage; +import com.android.monkeyrunner.core.IMonkeyDevice; import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; @@ -61,7 +61,7 @@ public class MonkeyControllerFrame extends JFrame { } }); - private final MonkeyDevice device; + private final IMonkeyDevice device; private class PressAction extends AbstractAction { private final PhysicalButton button; @@ -85,7 +85,7 @@ public class MonkeyControllerFrame extends JFrame { return button; } - public MonkeyControllerFrame(MonkeyDevice device) { + public MonkeyControllerFrame(IMonkeyDevice device) { super("MonkeyController"); this.device = device; @@ -155,7 +155,7 @@ public class MonkeyControllerFrame extends JFrame { } private void updateScreen() { - MonkeyImage snapshot = device.takeSnapshot(); + IMonkeyImage snapshot = device.takeSnapshot(); currentImage = snapshot.createBufferedImage(); imageLabel.setIcon(new ImageIcon(currentImage)); diff --git a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyBackend.java index 216d214..3c1b943 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerBackend.java +++ b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyBackend.java @@ -13,13 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.monkeyrunner; +package com.android.monkeyrunner.core; + +import com.android.monkeyrunner.MonkeyDevice; /** * Interface between MonkeyRunner common code and the MonkeyRunner backend. The backend is * responsible for communicating between the host and the device. */ -public interface MonkeyRunnerBackend { +public interface IMonkeyBackend { + /** + * Wait for a default device to connect to the backend. + * + * @return the connected device (or null if timeout); + */ + IMonkeyDevice waitForConnection(); + /** * Wait for a device to connect to the backend. * @@ -27,7 +36,7 @@ public interface MonkeyRunnerBackend { * @param deviceIdRegex the regular expression to specify which device to wait for. * @return the connected device (or null if timeout); */ - MonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex); + IMonkeyDevice waitForConnection(long timeoutMs, String deviceIdRegex); /** * Shutdown the backend and cleanup any resources it was using. diff --git a/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java new file mode 100644 index 0000000..c081a56 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyDevice.java @@ -0,0 +1,200 @@ +/* + * 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.monkeyrunner.core; + +import com.android.monkeyrunner.MonkeyManager; +import com.android.monkeyrunner.easy.HierarchyViewer; + +import java.util.Collection; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * MonkeyDevice interface. + */ +public interface IMonkeyDevice { + enum TouchPressType { + DOWN, UP, DOWN_AND_UP, + } + + /** + * Create a MonkeyMananger for talking to this device. + * + * @return the MonkeyManager + */ + MonkeyManager getManager(); + + /** + * Dispose of any native resources this device may have taken hold of. + */ + void dispose(); + + /** + * @return hierarchy viewer implementation for querying state of the view + * hierarchy. + */ + HierarchyViewer getHierarchyViewer(); + + /** + * Take the current screen's snapshot. + * @return the snapshot image + */ + IMonkeyImage takeSnapshot(); + + /** + * Reboot the device. + * + * @param into which bootloader to boot into. Null means default reboot. + */ + void reboot(@Nullable String into); + + /** + * Get device's property. + * + * @param key the property name + * @return the property value + */ + String getProperty(String key); + + /** + * Get system property. + * + * @param key the name of the system property + * @return the property value + */ + String getSystemProperty(String key); + + /** + * Perform a touch of the given type at (x,y). + * + * @param x the x coordinate + * @param y the y coordinate + * @param type the touch type + */ + void touch(int x, int y, TouchPressType type); + + /** + * Perform a press of a given type using a given key. + * + * TODO: define standard key names in a separate class or enum + * + * @param keyName the name of the key to use + * @param type the type of press to perform + */ + void press(String keyName, TouchPressType type); + + /** + * Perform a drag from one one location to another + * + * @param startx the x coordinate of the drag's starting point + * @param starty the y coordinate of the drag's starting point + * @param endx the x coordinate of the drag's end point + * @param endy the y coordinate of the drag's end point + * @param steps the number of steps to take when interpolating points + * @param ms the duration of the drag + */ + void drag(int startx, int starty, int endx, int endy, int steps, long ms); + + /** + * Type a given string. + * + * @param string the string to type + */ + void type(String string); + + /** + * Execute a shell command. + * + * @param cmd the command to execute + * @return the output of the command + */ + String shell(String cmd); + + /** + * Install a given package. + * + * @param path the path to the installation package + * @return true if success + */ + boolean installPackage(String path); + + /** + * Uninstall a given package. + * + * @param packageName the name of the package + * @return true if success + */ + boolean removePackage(String packageName); + + /** + * Start an activity. + * + * @param uri the URI for the Intent + * @param action the action for the Intent + * @param data the data URI for the Intent + * @param mimeType the mime type for the Intent + * @param categories the category names for the Intent + * @param extras the extras to add to the Intent + * @param component the component of the Intent + * @param flags the flags for the Intent + */ + void startActivity(@Nullable String uri, @Nullable String action, + @Nullable String data, @Nullable String mimeType, + Collection<String> categories, Map<String, Object> extras, @Nullable String component, + int flags); + + /** + * Send a broadcast intent to the device. + * + * @param uri the URI for the Intent + * @param action the action for the Intent + * @param data the data URI for the Intent + * @param mimeType the mime type for the Intent + * @param categories the category names for the Intent + * @param extras the extras to add to the Intent + * @param component the component of the Intent + * @param flags the flags for the Intent + */ + void broadcastIntent(@Nullable String uri, @Nullable String action, + @Nullable String data, @Nullable String mimeType, + Collection<String> categories, Map<String, Object> extras, @Nullable String component, + int flags); + + /** + * Run the specified package with instrumentation and return the output it + * generates. + * + * Use this to run a test package using InstrumentationTestRunner. + * + * @param packageName The class to run with instrumentation. The format is + * packageName/className. Use packageName to specify the Android package to + * run, and className to specify the class to run within that package. For + * test packages, this is usually testPackageName/InstrumentationTestRunner + * @param args a map of strings to objects containing the arguments to pass + * to this instrumentation. + * @return A map of strings to objects for the output from the package. + * For a test package, contains a single key-value pair: the key is 'stream' + * and the value is a string containing the test output. + */ + Map<String, Object> instrument(String packageName, + Map<String, Object> args); + + /** + * Wake up the screen on the device. + */ + void wake(); +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java new file mode 100644 index 0000000..5a24fa7 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/core/IMonkeyImage.java @@ -0,0 +1,36 @@ +/* + * 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.monkeyrunner.core; + +import java.awt.image.BufferedImage; + +/** + * MonkeyImage interface. + * + * This interface defines an image representing a screen snapshot. + */ +public interface IMonkeyImage { + // TODO: add java docs + BufferedImage createBufferedImage(); + BufferedImage getBufferedImage(); + + IMonkeyImage getSubImage(int x, int y, int w, int h); + + byte[] convertToBytes(String format); + boolean writeToFile(String path, String format); + int getPixel(int x, int y); + boolean sameAs(IMonkeyImage other, double percent); +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java b/monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java new file mode 100644 index 0000000..04ccb93 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/core/MonkeyImageBase.java @@ -0,0 +1,219 @@ +/* + * 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.monkeyrunner.core; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; + +/** + * Base class with basic functionality for MonkeyImage implementations. + */ +public abstract class MonkeyImageBase implements IMonkeyImage { + private static Logger LOG = Logger.getLogger(MonkeyImageBase.class.getCanonicalName()); + + /** + * Convert the MonkeyImage to a BufferedImage. + * + * @return a BufferedImage for this MonkeyImage. + */ + @Override + public abstract BufferedImage createBufferedImage(); + + // Cache the BufferedImage so we don't have to generate it every time. + private WeakReference<BufferedImage> cachedBufferedImage = null; + + /** + * Utility method to handle getting the BufferedImage and managing the cache. + * + * @return the BufferedImage for this image. + */ + @Override + public BufferedImage getBufferedImage() { + // Check the cache first + if (cachedBufferedImage != null) { + BufferedImage img = cachedBufferedImage.get(); + if (img != null) { + return img; + } + } + + // Not in the cache, so create it and cache it. + BufferedImage img = createBufferedImage(); + cachedBufferedImage = new WeakReference<BufferedImage>(img); + return img; + } + + @Override + public byte[] convertToBytes(String format) { + BufferedImage argb = convertSnapshot(); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + ImageIO.write(argb, format, os); + } catch (IOException e) { + return new byte[0]; + } + return os.toByteArray(); + } + + @Override + public boolean writeToFile(String path, String format) { + if (format != null) { + return writeToFileHelper(path, format); + } + int offset = path.lastIndexOf('.'); + if (offset < 0) { + return writeToFileHelper(path, "png"); + } + String ext = path.substring(offset + 1); + Iterator<ImageWriter> writers = ImageIO.getImageWritersBySuffix(ext); + if (!writers.hasNext()) { + return writeToFileHelper(path, "png"); + } + ImageWriter writer = writers.next(); + BufferedImage image = convertSnapshot(); + try { + File f = new File(path); + f.delete(); + + ImageOutputStream outputStream = ImageIO.createImageOutputStream(f); + writer.setOutput(outputStream); + + try { + writer.write(image); + } finally { + writer.dispose(); + outputStream.flush(); + } + } catch (IOException e) { + return false; + } + return true; + } + + @Override + public int getPixel(int x, int y) { + BufferedImage image = getBufferedImage(); + return image.getRGB(x, y); + } + + private BufferedImage convertSnapshot() { + BufferedImage image = getBufferedImage(); + + // Convert the image to ARGB so ImageIO writes it out nicely + BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(), + BufferedImage.TYPE_INT_ARGB); + Graphics g = argb.createGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); + return argb; + } + + private boolean writeToFileHelper(String path, String format) { + BufferedImage argb = convertSnapshot(); + + try { + ImageIO.write(argb, format, new File(path)); + } catch (IOException e) { + return false; + } + return true; + } + + @Override + public boolean sameAs(IMonkeyImage other, double percent) { + BufferedImage otherImage = other.getBufferedImage(); + BufferedImage myImage = getBufferedImage(); + + // Easy size check + if (otherImage.getWidth() != myImage.getWidth()) { + return false; + } + if (otherImage.getHeight() != myImage.getHeight()) { + return false; + } + + int[] otherPixel = new int[1]; + int[] myPixel = new int[1]; + + int width = myImage.getWidth(); + int height = myImage.getHeight(); + + int numDiffPixels = 0; + // Now, go through pixel-by-pixel and check that the images are the same; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) { + numDiffPixels++; + } + } + } + double numberPixels = (height * width); + double diffPercent = numDiffPixels / numberPixels; + return percent <= 1.0 - diffPercent; + } + + // TODO: figure out the location of this class and is superclasses + private static class BufferedImageMonkeyImage extends MonkeyImageBase { + private final BufferedImage image; + + public BufferedImageMonkeyImage(BufferedImage image) { + this.image = image; + } + + @Override + public BufferedImage createBufferedImage() { + return image; + } + } + + public static IMonkeyImage loadImageFromFile(String path) { + File f = new File(path); + if (f.exists() && f.canRead()) { + try { + BufferedImage bufferedImage = ImageIO.read(new File(path)); + if (bufferedImage == null) { + LOG.log(Level.WARNING, "Cannot decode file %s", path); + return null; + } + return new BufferedImageMonkeyImage(bufferedImage); + } catch (IOException e) { + LOG.log(Level.WARNING, "Exception trying to decode image", e); + return null; + } + } else { + LOG.log(Level.WARNING, "Cannot read file %s", path); + return null; + } + } + + @Override + public IMonkeyImage getSubImage(int x, int y, int w, int h) { + BufferedImage image = getBufferedImage(); + return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h)); + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/By.java b/monkeyrunner/src/com/android/monkeyrunner/easy/By.java new file mode 100644 index 0000000..1ed1c6f --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/easy/By.java @@ -0,0 +1,85 @@ +/* + * 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.monkeyrunner.easy; + +import com.google.common.base.Preconditions; + +import com.android.hierarchyviewerlib.device.ViewNode; +import com.android.monkeyrunner.JythonUtils; +import com.android.monkeyrunner.doc.MonkeyRunnerExported; + +import org.python.core.ArgParser; +import org.python.core.ClassDictInit; +import org.python.core.PyObject; + +/** + * Select a view object based on some criteria. + * + * Currently supports the By.id criteria to search for an element by id. + * In the future it will support other criteria such as: + * By.classid - search by class. + * By.hash - search by hashcode + * and recursively searching under an already selected object. + * + * WARNING: This API is under development, expect the interface to change + * without notice. + * + * TODO: Implement other selectors, like classid, hash, and so on. + * TODO: separate java-only core from jython wrapper + */ +public class By extends PyObject implements ClassDictInit { + public static void classDictInit(PyObject dict) { + JythonUtils.convertDocAnnotationsForClass(By.class, dict); + } + + private String id; + + By(String id) { + this.id = id; + } + + @MonkeyRunnerExported(doc = "Select an object by id.", + args = { "id" }, + argDocs = { "The identifier of the object." }) + public static By id(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + String id = ap.getString(0); + return new By(id); + } + + public static By id(String id) { + return new By(id); + } + + /** + * Find the selected view from the root view node. + */ + ViewNode find(ViewNode rootNode) { + if (rootNode.id.equals(id)) { + return rootNode; + } + for (ViewNode child : rootNode.children) { + ViewNode found = find(child); + if (found != null) { + return found; + } + } + return null; + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java b/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java new file mode 100644 index 0000000..e72e462 --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/easy/EasyMonkeyDevice.java @@ -0,0 +1,228 @@ +/* + * 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.monkeyrunner.easy; + +import com.google.common.base.Preconditions; + +import com.android.hierarchyviewerlib.device.ViewNode; +import com.android.hierarchyviewerlib.device.ViewNode.Property; +import com.android.monkeyrunner.JythonUtils; +import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice.TouchPressType; +import com.android.monkeyrunner.doc.MonkeyRunnerExported; + +import org.eclipse.swt.graphics.Point; +import org.python.core.ArgParser; +import org.python.core.ClassDictInit; +import org.python.core.Py; +import org.python.core.PyException; +import org.python.core.PyInteger; +import org.python.core.PyObject; +import org.python.core.PyString; +import org.python.core.PyTuple; + +import java.util.Set; + +/** + * Extends {@link MonkeyDevice} to support looking up views using a 'selector'. + * Currently, only identifiers can be used as a selector. All methods on + * MonkeyDevice can be used on this class in Python. + * + * WARNING: This API is under development, expect the interface to change + * without notice. + */ +@MonkeyRunnerExported(doc = "MonkeyDevice with easier methods to refer to objects.") +public class EasyMonkeyDevice extends PyObject implements ClassDictInit { + public static void classDictInit(PyObject dict) { + JythonUtils.convertDocAnnotationsForClass(EasyMonkeyDevice.class, dict); + } + + private MonkeyDevice mDevice; + private HierarchyViewer mHierarchyViewer; + + private static final Set<String> EXPORTED_METHODS = + JythonUtils.getMethodNames(EasyMonkeyDevice.class); + + @MonkeyRunnerExported(doc = "Creates EasyMonkeyDevice with an underlying MonkeyDevice.", + args = { "device" }, + argDocs = { "MonkeyDevice to extend." }) + public EasyMonkeyDevice(MonkeyDevice device) { + this.mDevice = device; + this.mHierarchyViewer = device.getImpl().getHierarchyViewer(); + } + + @MonkeyRunnerExported(doc = "Sends a touch event to the selected object.", + args = { "selector", "type" }, + argDocs = { + "The selector identifying the object.", + "The event type as returned by TouchPressType()." }) + public void touch(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + By selector = getSelector(ap, 0); + String tmpType = ap.getString(1); + TouchPressType type = MonkeyDevice.TOUCH_NAME_TO_ENUM.get(tmpType); + if (type == null) type = TouchPressType.DOWN_AND_UP; + // TODO: try catch rethrow PyExc + touch(selector, type); + } + + public void touch(By selector, TouchPressType type) { + Point p = getElementCenter(selector); + mDevice.getImpl().touch(p.x, p.y, type); + } + + @MonkeyRunnerExported(doc = "Types a string into the specified object.", + args = { "selector", "text" }, + argDocs = { + "The selector identifying the object.", + "The text to type into the object." }) + public void type(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + By selector = getSelector(ap, 0); + String text = ap.getString(1); + type(selector, text); + } + + public void type(By selector, String text) { + Point p = getElementCenter(selector); + mDevice.getImpl().touch(p.x, p.y, TouchPressType.DOWN_AND_UP); + mDevice.getImpl().type(text); + } + + @MonkeyRunnerExported(doc = "Locates the coordinates of the selected object.", + args = { "selector" }, + argDocs = { "The selector identifying the object." }, + returns = "Tuple containing (x,y,w,h) location and size.") + public PyTuple locate(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + By selector = getSelector(ap, 0); + + ViewNode node = mHierarchyViewer.findView(selector); + Point p = HierarchyViewer.getAbsolutePositionOfView(node); + PyTuple tuple = new PyTuple( + new PyInteger(p.x), + new PyInteger(p.y), + new PyInteger(node.width), + new PyInteger(node.height)); + return tuple; + } + + @MonkeyRunnerExported(doc = "Checks if the specified object exists.", + args = { "selector" }, + returns = "True if the object exists.", + argDocs = { "The selector identifying the object." }) + public boolean exists(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + By selector = getSelector(ap, 0); + return exists(selector); + } + + public boolean exists(By selector) { + ViewNode node = mHierarchyViewer.findView(selector); + return node != null; + } + + @MonkeyRunnerExported(doc = "Checks if the specified object is visible.", + args = { "selector" }, + returns = "True if the object is visible.", + argDocs = { "The selector identifying the object." }) + public boolean visible(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + By selector = getSelector(ap, 0); + return visible(selector); + } + + public boolean visible(By selector) { + return mHierarchyViewer.visible(selector); + } + + @MonkeyRunnerExported(doc = "Obtain the text in the selected input box.", + args = { "selector" }, + argDocs = { "The selector identifying the object." }, + returns = "Text in the selected input box.") + public String getText(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + By selector = getSelector(ap, 0); + return getText(selector); + } + + public String getText(By selector) { + return mHierarchyViewer.getText(selector); + } + + @MonkeyRunnerExported(doc = "Gets the id of the focused window.", + returns = "The symbolic id of the focused window or None.") + public String getFocusedWindowId(PyObject[] args, String[] kws) { + return getFocusedWindowId(); + } + + public String getFocusedWindowId() { + return mHierarchyViewer.getFocusedWindowName(); + } + + /** + * Forwards unknown methods to the original MonkeyDevice object. + */ + @Override + public PyObject __findattr_ex__(String name) { + if (!EXPORTED_METHODS.contains(name)) { + return mDevice.__findattr_ex__(name); + } + return super.__findattr_ex__(name); + } + + /** + * Get the selector object from the argument parser. + * + * @param ap argument parser to get it from. + * @param i argument index. + * @return selector object. + */ + private By getSelector(ArgParser ap, int i) { + return (By)ap.getPyObject(i).__tojava__(By.class); + } + + /** + * Get the coordinates of the element's center. + * + * @param selector the element selector + * @return the (x,y) coordinates of the center + */ + private Point getElementCenter(By selector) { + ViewNode node = mHierarchyViewer.findView(selector); + if (node == null) { + throw new PyException(Py.ValueError, + String.format("View not found: %s", selector)); + } + + Point p = HierarchyViewer.getAbsoluteCenterOfView(node); + return p; + } + +}
\ No newline at end of file diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java b/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java new file mode 100644 index 0000000..450571c --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/easy/HierarchyViewer.java @@ -0,0 +1,158 @@ +/* + * 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.monkeyrunner.easy; + +import com.android.ddmlib.IDevice; +import com.android.ddmlib.Log; +import com.android.hierarchyviewerlib.device.DeviceBridge; +import com.android.hierarchyviewerlib.device.ViewNode; +import com.android.hierarchyviewerlib.device.Window; + +import org.eclipse.swt.graphics.Point; + +/** + * Class for querying the view hierarchy of the device. + */ +public class HierarchyViewer { + public static final String TAG = "hierarchyviewer"; + + private IDevice mDevice; + + /** + * Constructs the hierarchy viewer for the specified device. + * + * @param device The Android device to connect to. + */ + public HierarchyViewer(IDevice device) { + this.mDevice = device; + setupViewServer(); + } + + private void setupViewServer() { + DeviceBridge.setupDeviceForward(mDevice); + if (!DeviceBridge.isViewServerRunning(mDevice)) { + if (!DeviceBridge.startViewServer(mDevice)) { + // TODO: Get rid of this delay. + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + } + if (!DeviceBridge.startViewServer(mDevice)) { + Log.e(TAG, "Unable to debug device " + mDevice); + throw new RuntimeException("Could not connect to the view server"); + } + return; + } + } + DeviceBridge.loadViewServerInfo(mDevice); + } + + /** + * Finds a view using a selector. Currently only supports selectors which + * specify an id. + * + * @param selector selector for the view. + * @return view with the specified ID, or {@code null} if no view found. + */ + public ViewNode findView(By selector) { + ViewNode rootNode = DeviceBridge.loadWindowData( + new Window(mDevice, "", 0xffffffff)); + if (rootNode == null) { + throw new RuntimeException("Could not dump view"); + } + return selector.find(rootNode); + } + + /** + * Gets the window that currently receives the focus. + * + * @return name of the window that currently receives the focus. + */ + public String getFocusedWindowName() { + int id = DeviceBridge.getFocusedWindow(mDevice); + Window[] windows = DeviceBridge.loadWindows(mDevice); + for (Window w : windows) { + if (w.getHashCode() == id) + return w.getTitle(); + } + return null; + } + + /** + * Gets the absolute x/y position of the view node. + * + * @param node view node to find position of. + * @return point specifying the x/y position of the node. + */ + public static Point getAbsolutePositionOfView(ViewNode node) { + int x = node.left; + int y = node.top; + ViewNode p = node.parent; + while (p != null) { + x += p.left - p.scrollX; + y += p.top - p.scrollY; + p = p.parent; + } + return new Point(x, y); + } + + /** + * Gets the absolute x/y center of the specified view node. + * + * @param node view node to find position of. + * @return absolute x/y center of the specified view node. + */ + public static Point getAbsoluteCenterOfView(ViewNode node) { + Point point = getAbsolutePositionOfView(node); + return new Point( + point.x + (node.width / 2), point.y + (node.height / 2)); + } + + /** + * Gets the visibility of a given element. + * + * @param selector selector for the view. + * @return True if the element is visible. + */ + public boolean visible(By selector) { + ViewNode node = findView(selector); + boolean ret = (node != null) + && node.namedProperties.containsKey("getVisibility()") + && "VISIBLE".equalsIgnoreCase( + node.namedProperties.get("getVisibility()").value); + return ret; + + } + + /** + * Gets the text of a given element. + * + * @param selector selector for the view. + * @return the text of the given element. + */ + public String getText(By selector) { + ViewNode node = findView(selector); + if (node == null) { + throw new RuntimeException("Node not found"); + } + ViewNode.Property textProperty = node.namedProperties.get("text:mText"); + if (textProperty == null) { + throw new RuntimeException("No text property on node"); + } + return textProperty.value; + } +} diff --git a/monkeyrunner/src/com/android/monkeyrunner/easy/README b/monkeyrunner/src/com/android/monkeyrunner/easy/README new file mode 100644 index 0000000..239bedd --- /dev/null +++ b/monkeyrunner/src/com/android/monkeyrunner/easy/README @@ -0,0 +1,27 @@ +com.android.monkeyrunner.easy contains classes intended to make it easier +to interact with applications using the MonkeyRunner framework. Instead of +referencing a button or input box by x,y coordinate, they can be referenced +by identifier, as in the following Python example: + +############################################################################## + +from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice +from com.android.monkeyrunner.easy import EasyMonkeyDevice +from com.android.monkeyrunner.easy import By + +# Connect to the current device. +device = MonkeyRunner.waitForConnection() + +# Use the EasyMonkey API, all methods on device are available in easy_device. +easy_device = EasyMonkeyDevice(device) + +if not easy_device.visible(By.id('id/all_apps_button')): + raise Error('Could not find the "all apps" button') + +print "Location of element:", easy_device.locate(By.id('id/all_apps_button')) + +easy_device.touch(By.id('id/all_apps_button'), 'DOWN_AND_UP') + +############################################################################## + +WARNING: This API is under development and may change without notice. diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java index c1a8f7f..914a5b9 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorder.java @@ -15,8 +15,9 @@ */ package com.android.monkeyrunner.recorder; -import com.android.monkeyrunner.MonkeyDevice; import com.android.monkeyrunner.adb.AdbBackend; +import com.android.monkeyrunner.core.IMonkeyBackend; +import com.android.monkeyrunner.core.IMonkeyDevice; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; @@ -44,7 +45,7 @@ public class MonkeyRecorder { * * @param device */ - public static void start(final MonkeyDevice device) { + public static void start(final IMonkeyDevice device) { MonkeyRecorderFrame frame = new MonkeyRecorderFrame(device); // TODO: this is a hack until the window listener works. frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); @@ -69,7 +70,7 @@ public class MonkeyRecorder { } public static void main(String[] args) { - AdbBackend adb = new AdbBackend(); + IMonkeyBackend adb = new AdbBackend(); MonkeyRecorder.start(adb.waitForConnection()); } } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java index b6c1f78..88c1e16 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/MonkeyRecorderFrame.java @@ -16,7 +16,8 @@ package com.android.monkeyrunner.recorder; import com.android.monkeyrunner.MonkeyDevice; -import com.android.monkeyrunner.MonkeyImage; +import com.android.monkeyrunner.core.IMonkeyImage; +import com.android.monkeyrunner.core.IMonkeyDevice; import com.android.monkeyrunner.recorder.actions.Action; import com.android.monkeyrunner.recorder.actions.DragAction; import com.android.monkeyrunner.recorder.actions.DragAction.Direction; @@ -60,7 +61,7 @@ public class MonkeyRecorderFrame extends JFrame { private static final Logger LOG = Logger.getLogger(MonkeyRecorderFrame.class.getName()); - private final MonkeyDevice device; + private final IMonkeyDevice device; private static final long serialVersionUID = 1L; private JPanel jContentPane = null; @@ -83,6 +84,7 @@ public class MonkeyRecorderFrame extends JFrame { private ActionListModel actionListModel; private final Timer refreshTimer = new Timer(1000, new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { refreshDisplay(); // @jve:decl-index=0: } @@ -91,7 +93,7 @@ public class MonkeyRecorderFrame extends JFrame { /** * This is the default constructor */ - public MonkeyRecorderFrame(MonkeyDevice device) { + public MonkeyRecorderFrame(IMonkeyDevice device) { this.device = device; initialize(); } @@ -110,7 +112,7 @@ public class MonkeyRecorderFrame extends JFrame { } private void refreshDisplay() { - MonkeyImage snapshot = device.takeSnapshot(); + IMonkeyImage snapshot = device.takeSnapshot(); currentImage = snapshot.createBufferedImage(); Graphics2D g = scaledImage.createGraphics(); @@ -200,6 +202,7 @@ public class MonkeyRecorderFrame extends JFrame { waitButton = new JButton(); waitButton.setText("Wait"); waitButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { String howLongStr = JOptionPane.showInputDialog("How many seconds to wait?"); if (howLongStr != null) { @@ -222,6 +225,7 @@ public class MonkeyRecorderFrame extends JFrame { pressButton = new JButton(); pressButton.setText("Press a Button"); pressButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { JPanel panel = new JPanel(); JLabel text = new JLabel("What button to press?"); @@ -255,6 +259,7 @@ public class MonkeyRecorderFrame extends JFrame { typeButton = new JButton(); typeButton.setText("Type Something"); typeButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { String whatToType = JOptionPane.showInputDialog("What to type?"); if (whatToType != null) { @@ -276,6 +281,7 @@ public class MonkeyRecorderFrame extends JFrame { flingButton = new JButton(); flingButton.setText("Fling"); flingButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); @@ -358,6 +364,7 @@ public class MonkeyRecorderFrame extends JFrame { exportActionButton = new JButton(); exportActionButton.setText("Export Actions"); exportActionButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent ev) { JFileChooser fc = new JFileChooser(); if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { @@ -383,6 +390,7 @@ public class MonkeyRecorderFrame extends JFrame { refreshButton = new JButton(); refreshButton.setText("Refresh Display"); refreshButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { refreshDisplay(); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java index d582aa4..6fa91ab 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/Action.java @@ -15,7 +15,7 @@ */ package com.android.monkeyrunner.recorder.actions; -import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice; /** * All actions that can be recorded must implement this interface. @@ -41,5 +41,5 @@ public interface Action { * * @param device the device to execute the action on. */ - void execute(MonkeyDevice device) throws Exception; + void execute(IMonkeyDevice device) throws Exception; } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java index 082bfe4..2461c0d 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/DragAction.java @@ -15,7 +15,7 @@ */ package com.android.monkeyrunner.recorder.actions; -import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice; /** * Action to drag the "finger" across the device. @@ -77,7 +77,7 @@ public class DragAction implements Action { } @Override - public void execute(MonkeyDevice device) { + public void execute(IMonkeyDevice device) { device.drag(startx, starty, endx, endy, steps, timeMs); } } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java index a0d9e0e..66a933a 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/PressAction.java @@ -19,6 +19,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice; /** * Action to press a certain button. @@ -60,7 +61,7 @@ public class PressAction implements Action { } @Override - public void execute(MonkeyDevice device) { + public void execute(IMonkeyDevice device) { device.press(key, MonkeyDevice.TOUCH_NAME_TO_ENUM.get(downUpFlag)); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java index 4633edb..4e0ae2d 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TouchAction.java @@ -19,6 +19,7 @@ import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice; /** * Action to touch the touchscreen at a certain location. @@ -46,7 +47,7 @@ public class TouchAction implements Action { } @Override - public void execute(MonkeyDevice device) throws Exception { + public void execute(IMonkeyDevice device) throws Exception { device.touch(x, y, MonkeyDevice.TOUCH_NAME_TO_ENUM.get(direction)); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java index 1bfb9e9..78e90b0 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/TypeAction.java @@ -15,7 +15,7 @@ */ package com.android.monkeyrunner.recorder.actions; -import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice; /** * Action to type in a string on the device. @@ -34,12 +34,13 @@ public class TypeAction implements Action { @Override public String serialize() { - String pydict = PyDictUtilBuilder.newBuilder().add("message", whatToType).build(); + String pydict = PyDictUtilBuilder.newBuilder() + .add("message", whatToType).build(); return "TYPE|" + pydict; } @Override - public void execute(MonkeyDevice device) { + public void execute(IMonkeyDevice device) { device.type(whatToType); } } diff --git a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java index 9115f9a..bd2d421 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java +++ b/monkeyrunner/src/com/android/monkeyrunner/recorder/actions/WaitAction.java @@ -15,7 +15,7 @@ */ package com.android.monkeyrunner.recorder.actions; -import com.android.monkeyrunner.MonkeyDevice; +import com.android.monkeyrunner.core.IMonkeyDevice; /** * Action that specifies to wait for a certain amount of time. @@ -27,19 +27,16 @@ public class WaitAction implements Action { this.howLongSeconds = howLongSeconds; } - @Override public String getDisplayName() { return String.format("Wait for %g seconds", this.howLongSeconds); } - @Override public String serialize() { String pydict = PyDictUtilBuilder.newBuilder().add("seconds", howLongSeconds).build(); return "WAIT|" + pydict; } - @Override - public void execute(MonkeyDevice device) throws Exception { + public void execute(IMonkeyDevice device) throws Exception { long ms = (long) (1000.0f * howLongSeconds); Thread.sleep(ms); } diff --git a/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java b/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java index c2fa5f7..b868bf1 100644 --- a/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java +++ b/monkeyrunner/src/com/android/monkeyrunner/stub/StubBackend.java @@ -15,18 +15,22 @@ */ package com.android.monkeyrunner.stub; -import com.android.monkeyrunner.MonkeyDevice; import com.android.monkeyrunner.MonkeyManager; -import com.android.monkeyrunner.MonkeyRunnerBackend; - -public class StubBackend implements MonkeyRunnerBackend { +import com.android.monkeyrunner.core.IMonkeyBackend; +import com.android.monkeyrunner.core.IMonkeyDevice; +public class StubBackend implements IMonkeyBackend { public MonkeyManager createManager(String address, int port) { // TODO Auto-generated method stub return null; } - public MonkeyDevice waitForConnection(long timeout, String deviceId) { + public IMonkeyDevice waitForConnection() { + // TODO Auto-generated method stub + return null; + } + + public IMonkeyDevice waitForConnection(long timeout, String deviceId) { // TODO Auto-generated method stub return null; } diff --git a/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java b/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java index 5b8c8f9..5f781f1 100644 --- a/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java +++ b/monkeyrunner/test/com/android/monkeyrunner/JythonUtilsTest.java @@ -23,6 +23,7 @@ import com.android.monkeyrunner.doc.MonkeyRunnerExported; import junit.framework.TestCase; import org.python.core.ArgParser; +import org.python.core.ClassDictInit; import org.python.core.PyDictionary; import org.python.core.PyException; import org.python.core.PyObject; @@ -30,6 +31,7 @@ import org.python.core.PyString; import java.util.List; import java.util.Map; +import java.util.Set; /** * Unit tests for the JythonUtils class. @@ -37,6 +39,7 @@ import java.util.Map; public class JythonUtilsTest extends TestCase { private static final String PACKAGE_NAME = JythonUtilsTest.class.getPackage().getName(); private static final String CLASS_NAME = JythonUtilsTest.class.getSimpleName(); + private static final String EXECUTABLE_PATH = "string"; private static boolean called = false; private static double floatValue = 0.0; @@ -102,7 +105,7 @@ public class JythonUtilsTest extends TestCase { } sb.append(")"); - return ScriptRunner.runStringAndGet(sb.toString(), "result").get("result"); + return ScriptRunner.runStringAndGet(EXECUTABLE_PATH, sb.toString(), "result").get("result"); } public void testSimpleCall() { @@ -221,4 +224,34 @@ public class JythonUtilsTest extends TestCase { double d = (Double) doublePyObject.__tojava__(Double.class); assertEquals(3.14, d); } + + /** + * Base class to test overridden methods. + */ + static class PythonMethodsClass extends PyObject implements ClassDictInit { + public static void classDictInit(PyObject dict) { + JythonUtils.convertDocAnnotationsForClass(PythonMethodsClass.class, dict); + } + + @MonkeyRunnerExported(doc = "The first method.") + public void firstMethod(PyObject[] args, String[] kws) { + } + + @MonkeyRunnerExported(doc = "The second method.") + public void secondMethod(PyObject[] args, String[] kws) { + } + + public void unattributedMethod() { + } + } + + public void testGetPythonMethods() { + Set<String> methods = JythonUtils.getMethodNames(PythonMethodsClass.class); + assertEquals(2, methods.size()); + assertTrue(methods.contains("firstMethod")); + assertTrue(methods.contains("secondMethod")); + + // Make sure it works on non-Jython objects. + assertTrue(JythonUtils.getMethodNames(String.class).isEmpty()); + } } diff --git a/ninepatch/Android.mk b/ninepatch/Android.mk index 42e0205..5f6cbed 100644 --- a/ninepatch/Android.mk +++ b/ninepatch/Android.mk @@ -21,3 +21,6 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_MODULE := ninepatch include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/ninepatch/NOTICE b/ninepatch/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/ninepatch/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/ninepatch/tests/.classpath b/ninepatch/tests/.classpath new file mode 100644 index 0000000..26542d3 --- /dev/null +++ b/ninepatch/tests/.classpath @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry combineaccessrules="false" kind="src" path="/ninepatch"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/ninepatch/tests/.project b/ninepatch/tests/.project new file mode 100644 index 0000000..3d049cf --- /dev/null +++ b/ninepatch/tests/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>ninepatch-tests</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/hierarchyviewer2/app/src/Android.mk b/ninepatch/tests/Android.mk index 1f15bee..8a9fd71 100644 --- a/hierarchyviewer2/app/src/Android.mk +++ b/ninepatch/tests/Android.mk @@ -13,21 +13,21 @@ # limitations under the License. LOCAL_PATH := $(call my-dir) + include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := res -LOCAL_JAR_MANIFEST := ../etc/manifest.txt -LOCAL_JAVA_LIBRARIES := \ - ddmlib \ - ddmuilib \ - hierarchyviewerlib \ - swt \ - org.eclipse.jface_3.4.2.M20090107-0800 \ - org.eclipse.core.commands_3.4.0.I20080509-2000 \ - sdklib +LOCAL_MODULE := ninepatch-tests +LOCAL_MODULE_TAGS := optional -LOCAL_MODULE := hierarchyviewer2 +LOCAL_JAVA_LIBRARIES := junit +# bundle ninepatch inside the test jar for continuous tests +LOCAL_STATIC_JAVA_LIBRARIES := ninepatch include $(BUILD_HOST_JAVA_LIBRARY) +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/ninepatch/tests/res/com/android/ninepatch/button.9.png b/ninepatch/tests/res/com/android/ninepatch/button.9.png Binary files differnew file mode 100644 index 0000000..9d52f40 --- /dev/null +++ b/ninepatch/tests/res/com/android/ninepatch/button.9.png diff --git a/ninepatch/tests/src/com/android/ninepatch/NinePatchTest.java b/ninepatch/tests/src/com/android/ninepatch/NinePatchTest.java new file mode 100644 index 0000000..1722b55 --- /dev/null +++ b/ninepatch/tests/src/com/android/ninepatch/NinePatchTest.java @@ -0,0 +1,48 @@ +/* + * 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.ninepatch; + +import java.io.InputStream; + +import junit.framework.TestCase; + +public class NinePatchTest extends TestCase { + + private NinePatch mPatch; + + @Override + protected void setUp() throws Exception { + InputStream stream = this.getClass().getResourceAsStream("button.9.png"); + + mPatch = NinePatch.load(stream, true /* is9Patch*/, false /* convert */); + } + + public void test9PatchLoad() throws Exception { + assertNotNull(mPatch); + } + + public void test9PatchMinSize() { + int[] padding = new int[4]; + mPatch.getPadding(padding); + assertEquals(13, padding[0]); + assertEquals(3, padding[1]); + assertEquals(13, padding[2]); + assertEquals(4, padding[3]); + assertEquals(36, mPatch.getWidth()); + assertEquals(25, mPatch.getHeight()); + } +} diff --git a/screenshot/NOTICE b/screenshot/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/screenshot/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/sdklauncher/NOTICE b/sdklauncher/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/sdklauncher/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/sdkmanager/app/.classpath b/sdkmanager/app/.classpath index 3380ba1..50576bb 100644 --- a/sdkmanager/app/.classpath +++ b/sdkmanager/app/.classpath @@ -1,12 +1,14 @@ -<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
- <classpathentry kind="src" path="src"/>
- <classpathentry kind="src" path="tests"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
- <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
- <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
- <classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
- <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
- <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/>
- <classpathentry kind="output" path="bin"/>
-</classpath>
+<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry excluding="**/Android.mk" kind="src" path="src"/> + <classpathentry excluding="**/Android.mk" kind="src" path="tests"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> + <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/> + <classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/> + <classpathentry combineaccessrules="false" kind="src" path="/common"/> + <classpathentry kind="output" path="bin"/> +</classpath> + diff --git a/sdkmanager/app/.settings/org.eclipse.jdt.core.prefs b/sdkmanager/app/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..a363b10 --- /dev/null +++ b/sdkmanager/app/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 16 15:11:09 PDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/sdkmanager/app/Android.mk b/sdkmanager/app/Android.mk index 24ba61f..d7b630e 100644 --- a/sdkmanager/app/Android.mk +++ b/sdkmanager/app/Android.mk @@ -1,5 +1,33 @@ # Copyright 2007 The Android Open Source Project # -SDKMANAGERAPP_LOCAL_DIR := $(call my-dir) -include $(SDKMANAGERAPP_LOCAL_DIR)/etc/Android.mk -include $(SDKMANAGERAPP_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := etc/manifest.txt + +# IMPORTANT: if you add a new dependency here, please make sure +# to also check the following files: +# sdkmanager/app/etc/manifest.txt +# sdkmanager/app/etc/android.bat +# (Note that we don't reference swt.jar in these files since +# it is dynamically added by android.bat/.sh based on whether the +# current VM is 32 or 64 bit.) +LOCAL_JAVA_LIBRARIES := \ + androidprefs \ + sdklib \ + sdkuilib \ + swt \ + org.eclipse.jface_3.4.2.M20090107-0800 \ + org.eclipse.equinox.common_3.4.0.v20080421-2006 \ + org.eclipse.core.commands_3.4.0.I20080509-2000 + +LOCAL_MODULE := sdkmanager + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/sdkmanager/app/NOTICE b/sdkmanager/app/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/sdkmanager/app/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/sdkmanager/app/etc/android b/sdkmanager/app/etc/android index 2e17009..555e071 100755 --- a/sdkmanager/app/etc/android +++ b/sdkmanager/app/etc/android @@ -74,9 +74,10 @@ fi if [ "$OSTYPE" = "cygwin" ] ; then jarpath=`cygpath -w "$frameworkdir/$jarfile"` + jarpath="$jarpath;"`cygpath -w "$frameworkdir/swtmenubar.jar"` progdir=`cygpath -w "$progdir"` else - jarpath="$frameworkdir/$jarfile" + jarpath="$frameworkdir/$jarfile:$frameworkdir/swtmenubar.jar" fi # Get the current content of java.ext.dirs so that we can add to it instead of replacing @@ -105,9 +106,9 @@ if [ ! -d "$swtpath" ]; then exit 1 fi -if [ -z "$1" ]; then - echo "Starting Android SDK and AVD Manager" -fi - # need to use "java.ext.dirs" because "-jar" causes classpath to be ignored -exec "$java_cmd" -Xmx256M $os_opts $java_debug -Dcom.android.sdkmanager.toolsdir="$progdir" -classpath "$jarpath:$swtpath/swt.jar" com.android.sdkmanager.Main "$@" +exec "$java_cmd" \ + -Xmx256M $os_opts $java_debug \ + -Dcom.android.sdkmanager.toolsdir="$progdir" \ + -classpath "$jarpath:$swtpath/swt.jar" \ + com.android.sdkmanager.Main "$@" diff --git a/sdkmanager/app/etc/android.bat b/sdkmanager/app/etc/android.bat index daa6b8a..0d83734 100755 --- a/sdkmanager/app/etc/android.bat +++ b/sdkmanager/app/etc/android.bat @@ -37,7 +37,7 @@ set java_exe= call lib\find_java.bat
if not defined java_exe goto :EOF
-set jar_path=lib\sdkmanager.jar
+set jar_path=lib\sdkmanager.jar;lib\swtmenubar.jar
rem Set SWT.Jar path based on current architecture (x86 or x86_64)
for /f %%a in ('%java_exe% -jar lib\archquery.jar') do set swt_path=lib\%%a
@@ -45,7 +45,7 @@ for /f %%a in ('%java_exe% -jar lib\archquery.jar') do set swt_path=lib\%%a if "%1 %2"=="update sdk" goto StartUi
if not "%1"=="" goto EndTempCopy
:StartUi
- echo [INFO] Starting Android SDK and AVD Manager
+ rem Starting Android SDK and AVD Manager UI
rem We're now going to create a temp dir to hold all the Jar files needed
rem to run the android tool, copy them in the temp dir and finally execute
diff --git a/sdkmanager/app/etc/manifest.txt b/sdkmanager/app/etc/manifest.txt index 51845c7..bb2e8c4 100644 --- a/sdkmanager/app/etc/manifest.txt +++ b/sdkmanager/app/etc/manifest.txt @@ -1,2 +1,2 @@ Main-Class: com.android.sdkmanager.Main -Class-Path: androidprefs.jar common.jar sdklib.jar sdkuilib.jar org.eclipse.jface_3.4.2.M20090107-0800.jar org.eclipse.equinox.common_3.4.0.v20080421-2006.jar org.eclipse.core.commands_3.4.0.I20080509-2000.jar +Class-Path: androidprefs.jar common.jar sdklib.jar sdkuilib.jar swtmenubar.jar org.eclipse.jface_3.4.2.M20090107-0800.jar org.eclipse.equinox.common_3.4.0.v20080421-2006.jar org.eclipse.core.commands_3.4.0.I20080509-2000.jar diff --git a/sdkmanager/app/src/Android.mk b/sdkmanager/app/src/Android.mk deleted file mode 100644 index 7520f96..0000000 --- a/sdkmanager/app/src/Android.mk +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2007 The Android Open Source Project -# -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := . - -LOCAL_JAR_MANIFEST := ../etc/manifest.txt - -# If the dependency list is changed, etc/manifest.txt -# MUST be updated as well (Except for swt.jar which is dynamically -# added based on whether the VM is 32 or 64 bit) -LOCAL_JAVA_LIBRARIES := \ - androidprefs \ - sdklib \ - sdkuilib \ - swt \ - org.eclipse.jface_3.4.2.M20090107-0800 \ - org.eclipse.equinox.common_3.4.0.v20080421-2006 \ - org.eclipse.core.commands_3.4.0.I20080509-2000 - -LOCAL_MODULE := sdkmanager - -include $(BUILD_HOST_JAVA_LIBRARY) - diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java index 8f5dec4..50fc496 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java +++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java @@ -47,10 +47,10 @@ class CommandLineProcessor { */ /** Internal verb name for internally hidden flags. */ - public final static String GLOBAL_FLAG_VERB = "@@internal@@"; + public final static String GLOBAL_FLAG_VERB = "@@internal@@"; //$NON-NLS-1$ /** String to use when the verb doesn't need any object. */ - public final static String NO_VERB_OBJECT = ""; + public final static String NO_VERB_OBJECT = ""; //$NON-NLS-1$ /** The global help flag. */ public static final String KEY_HELP = "help"; @@ -183,7 +183,7 @@ class CommandLineProcessor { public Object getValue(String verb, String directObject, String longFlagName) { if (verb != null && directObject != null) { - String key = verb + "/" + directObject + "/" + longFlagName; + String key = verb + '/' + directObject + '/' + longFlagName; Arg arg = mArguments.get(key); return arg.getCurrentValue(); } @@ -216,7 +216,7 @@ class CommandLineProcessor { * argument mode. */ protected void setValue(String verb, String directObject, String longFlagName, Object value) { - String key = verb + "/" + directObject + "/" + longFlagName; + String key = verb + '/' + directObject + '/' + longFlagName; Arg arg = mArguments.get(key); arg.setCurrentValue(value); } @@ -238,16 +238,16 @@ class CommandLineProcessor { for (int i = 0; i < n; i++) { Arg arg = null; String a = args[i]; - if (a.startsWith("--")) { + if (a.startsWith("--")) { //$NON-NLS-1$ arg = findLongArg(verb, directObject, a.substring(2)); - } else if (a.startsWith("-")) { + } else if (a.startsWith("-")) { //$NON-NLS-1$ arg = findShortArg(verb, directObject, a.substring(1)); } // No matching argument name found if (arg == null) { // Does it looks like a dashed parameter? - if (a.startsWith("-")) { + if (a.startsWith("-")) { //$NON-NLS-1$ if (verb == null || directObject == null) { // It looks like a dashed parameter and we don't have a a verb/object // set yet, the parameter was just given too early. @@ -330,9 +330,9 @@ class CommandLineProcessor { String b = args[i]; Arg dummyArg = null; - if (b.startsWith("--")) { + if (b.startsWith("--")) { //$NON-NLS-1$ dummyArg = findLongArg(verb, directObject, b.substring(2)); - } else if (b.startsWith("-")) { + } else if (b.startsWith("-")) { //$NON-NLS-1$ dummyArg = findShortArg(verb, directObject, b.substring(1)); } if (dummyArg != null) { @@ -352,7 +352,7 @@ class CommandLineProcessor { // used to print specific help. // Setting a non-null error message triggers printing the help, however // there is no specific error to print. - errorMsg = ""; + errorMsg = ""; //$NON-NLS-1$ } } @@ -392,9 +392,9 @@ class CommandLineProcessor { arg.getDirectObject().equals(directObject)) { if (arg.isMandatory() && arg.getCurrentValue() == null) { if (missing == null) { - missing = "--" + arg.getLongArg(); + missing = "--" + arg.getLongArg(); //$NON-NLS-1$ } else { - missing += ", --" + arg.getLongArg(); + missing += ", --" + arg.getLongArg(); //$NON-NLS-1$ plural = true; } } @@ -432,7 +432,7 @@ class CommandLineProcessor { if (directObject == null) { directObject = NO_VERB_OBJECT; } - String key = verb + "/" + directObject + "/" + longName; + String key = verb + '/' + directObject + '/' + longName; //$NON-NLS-1$ return mArguments.get(key); } @@ -497,7 +497,7 @@ class CommandLineProcessor { "\n" + "Global options:", verb == null ? "action" : - verb + (directObject == null ? "" : " " + directObject)); + verb + (directObject == null ? "" : " " + directObject)); //$NON-NLS-1$ listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT); if (verb == null || directObject == null) { @@ -552,8 +552,8 @@ class CommandLineProcessor { Arg arg = entry.getValue(); if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) { - String value = ""; - String required = ""; + String value = ""; //$NON-NLS-1$ + String required = ""; //$NON-NLS-1$ if (arg.isMandatory()) { required = " [required]"; @@ -828,7 +828,7 @@ class CommandLineProcessor { * Internal helper to define a new argument for a give action. * * @param mode The {@link Mode} for the argument. - * @param mandatory The argument is required (never if {@link Mode.BOOLEAN}) + * @param mandatory The argument is required (never if {@link Mode#BOOLEAN}) * @param verb The verb name. Can be #INTERNAL_VERB. * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG. * @param shortName The one-letter short argument name. Can be empty but not null. @@ -853,7 +853,7 @@ class CommandLineProcessor { directObject = NO_VERB_OBJECT; } - String key = verb + "/" + directObject + "/" + longName; + String key = verb + '/' + directObject + '/' + longName; mArguments.put(key, new Arg(mode, mandatory, verb, directObject, shortName, longName, description, defaultValue)); } @@ -874,7 +874,7 @@ class CommandLineProcessor { * @param args Format arguments. */ protected void stdout(String format, Object...args) { - mLog.printf(format + "\n", args); + mLog.printf(format + '\n', args); } /** diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java index 1fe6d97..5ac5f4c 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/Main.java +++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java @@ -16,30 +16,35 @@ package com.android.sdkmanager; +import com.android.annotations.VisibleForTesting; +import com.android.annotations.VisibleForTesting.Visibility; +import com.android.io.FileWrapper; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.IAndroidTarget.IOptionalLibrary; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; +import com.android.sdklib.IAndroidTarget.IOptionalLibrary; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdklib.internal.avd.HardwareProperties; import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty; import com.android.sdklib.internal.project.ProjectCreator; -import com.android.sdklib.internal.project.ProjectCreator.OutputLevel; import com.android.sdklib.internal.project.ProjectProperties; +import com.android.sdklib.internal.project.ProjectCreator.OutputLevel; import com.android.sdklib.internal.project.ProjectProperties.PropertyType; -import com.android.sdklib.io.FileWrapper; +import com.android.sdklib.repository.SdkAddonConstants; import com.android.sdklib.repository.SdkRepoConstants; import com.android.sdklib.xml.AndroidXPathFactory; import com.android.sdkmanager.internal.repository.AboutPage; import com.android.sdkmanager.internal.repository.SettingsPage; -import com.android.sdkuilib.internal.repository.LocalPackagesPage; +import com.android.sdkuilib.internal.repository.PackagesPage; import com.android.sdkuilib.internal.repository.UpdateNoWindow; import com.android.sdkuilib.internal.widgets.MessageBoxLog; +import com.android.sdkuilib.repository.IUpdaterWindow; import com.android.sdkuilib.repository.UpdaterWindow; +import com.android.util.Pair; import org.eclipse.swt.widgets.Display; import org.xml.sax.InputSource; @@ -52,6 +57,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; @@ -220,6 +227,15 @@ public class Main { } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) { displayAvdList(); + } else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) { + // We don't support a specific GUI for this. + // If the user forces a gui mode to see this list, simply launch the regular GUI. + if (!mSdkCommandLine.getFlagNoUI(verb)) { + showMainWindow(false /*autoUpdate*/); + } else { + displayRemoteSdkListNoUI(); + } + } else { displayTargetList(); displayAvdList(); @@ -259,7 +275,7 @@ public class Main { updateExportProject(); } else if (SdkCommandLine.OBJECT_SDK.equals(directObject)) { - if (mSdkCommandLine.getFlagNoUI()) { + if (mSdkCommandLine.getFlagNoUI(verb)) { updateSdkNoUI(); } else { showMainWindow(true /*autoUpdate*/); @@ -290,23 +306,19 @@ public class Main { */ private void showMainWindow(boolean autoUpdate) { try { - // display a message talking about the command line version - System.out.printf("No command line parameters provided, launching UI.\n" + - "See 'android --help' for operations from the command line.\n"); - MessageBoxLog errorLogger = new MessageBoxLog( "SDK Manager", Display.getCurrent(), true /*logErrorsOnly*/); - UpdaterWindow window = new UpdaterWindow( + IUpdaterWindow window = new UpdaterWindow( null /* parentShell */, errorLogger, mOsSdkFolder); window.registerPage("Settings", SettingsPage.class); window.registerPage("About", AboutPage.class); if (autoUpdate) { - window.setInitialPage(LocalPackagesPage.class); + window.setInitialPage(PackagesPage.class); window.setRequestAutoUpdate(true); } window.open(); @@ -318,6 +330,18 @@ public class Main { } } + private void displayRemoteSdkListNoUI() { + boolean force = mSdkCommandLine.getFlagForce(); + boolean useHttp = mSdkCommandLine.getFlagNoHttps(); + boolean obsolete = mSdkCommandLine.getFlagObsolete(); + String proxyHost = mSdkCommandLine.getParamProxyHost(); + String proxyPort = mSdkCommandLine.getParamProxyPort(); + + UpdateNoWindow upd = new UpdateNoWindow(mOsSdkFolder, mSdkManager, mSdkLog, + force, useHttp, proxyHost, proxyPort); + upd.listRemotePackages(obsolete); + } + /** * Updates the whole SDK without any UI, just using console output. */ @@ -326,40 +350,76 @@ public class Main { boolean useHttp = mSdkCommandLine.getFlagNoHttps(); boolean dryMode = mSdkCommandLine.getFlagDryMode(); boolean obsolete = mSdkCommandLine.getFlagObsolete(); - String proxyHost = mSdkCommandLine.getProxyHost(); - String proxyPort = mSdkCommandLine.getProxyPort(); + String proxyHost = mSdkCommandLine.getParamProxyHost(); + String proxyPort = mSdkCommandLine.getParamProxyPort(); // Check filter types. + Pair<String, ArrayList<String>> filterResult = + checkFilterValues(mSdkCommandLine.getParamFilter()); + if (filterResult.getFirst() != null) { + // We got an error. + errorAndExit(filterResult.getFirst()); + } + + UpdateNoWindow upd = new UpdateNoWindow(mOsSdkFolder, mSdkManager, mSdkLog, + force, useHttp, proxyHost, proxyPort); + upd.updateAll(filterResult.getSecond(), obsolete, dryMode); + } + + /** + * Checks the values from the filter parameter and returns a tuple + * (error , accepted values). Either error is null and accepted values is not, + * or the reverse. + * <p/> + * Note that this is a quick sanity check of the --filter parameter *before* we + * start loading the remote repository sources. Loading the remotes takes a while + * so it's worth doing a quick sanity check before hand. + * + * @param filter A comma-separated list of keywords + * @return A pair <error string, usable values>, only one must be null and the other non-null. + */ + @VisibleForTesting(visibility=Visibility.PRIVATE) + Pair<String, ArrayList<String>> checkFilterValues(String filter) { ArrayList<String> pkgFilter = new ArrayList<String>(); - String filter = mSdkCommandLine.getParamFilter(); + if (filter != null && filter.length() > 0) { + // Available types + Set<String> filterTypes = new TreeSet<String>(); + filterTypes.addAll(Arrays.asList(SdkRepoConstants.NODES)); + filterTypes.addAll(Arrays.asList(SdkAddonConstants.NODES)); + for (String t : filter.split(",")) { //$NON-NLS-1$ - if (t != null) { - t = t.trim(); - if (t.length() > 0) { - boolean found = false; - for (String t2 : SdkRepoConstants.NODES) { - if (t2.equals(t)) { - pkgFilter.add(t2); - found = true; - break; - } - } - if (!found) { - errorAndExit( - "Unknown package filter type '%1$s'.\nAccepted values are: %2$s", - t, - Arrays.toString(SdkRepoConstants.NODES)); - return; - } - } + if (t == null) { + continue; + } + t = t.trim(); + if (t.length() <= 0) { + continue; + } + + if (t.replaceAll("[0-9]+", "").length() == 0) { //$NON-NLS-1$ //$NON-NLS-2$ + // If the filter argument *only* contains digits, accept it. + // It's probably an index for the remote repository list, + // which we can't validate yet. + pkgFilter.add(t); + continue; + } + + if (filterTypes.contains(t)) { + pkgFilter.add(t); + continue; } + + return Pair.of( + String.format( + "Unknown package filter type '%1$s'.\nAccepted values are: %2$s", + t, + Arrays.toString(filterTypes.toArray())), + null); } } - UpdateNoWindow upd = new UpdateNoWindow(mOsSdkFolder, mSdkManager, mSdkLog, - force, useHttp, proxyHost, proxyPort); - upd.updateAll(pkgFilter, obsolete, dryMode); + return Pair.of(null, pkgFilter); } /** @@ -720,6 +780,18 @@ public class Main { * Displays the list of available Targets (Platforms and Add-ons) */ private void displayTargetList() { + + // Compact output, suitable for scripts. + if (mSdkCommandLine != null && mSdkCommandLine.getFlagCompact()) { + char eol = mSdkCommandLine.getFlagEolNull() ? '\0' : '\n'; + + for (IAndroidTarget target : mSdkManager.getTargets()) { + mSdkLog.printf("%1$s%2$c", target.hashString(), eol); + } + + return; + } + mSdkLog.printf("Available Android targets:\n"); int index = 1; @@ -798,16 +870,30 @@ public class Main { * @param avdManager */ public void displayAvdList(AvdManager avdManager) { - mSdkLog.printf("Available Android Virtual Devices:\n"); AvdInfo[] avds = avdManager.getValidAvds(); + + // Compact output, suitable for scripts. + if (mSdkCommandLine != null && mSdkCommandLine.getFlagCompact()) { + char eol = mSdkCommandLine.getFlagEolNull() ? '\0' : '\n'; + + for (int index = 0 ; index < avds.length ; index++) { + AvdInfo info = avds[index]; + mSdkLog.printf("%1$s%2$c", info.getName(), eol); + } + + return; + } + + mSdkLog.printf("Available Android Virtual Devices:\n"); + for (int index = 0 ; index < avds.length ; index++) { AvdInfo info = avds[index]; if (index > 0) { mSdkLog.printf("---------\n"); } mSdkLog.printf(" Name: %s\n", info.getName()); - mSdkLog.printf(" Path: %s\n", info.getPath()); + mSdkLog.printf(" Path: %s\n", info.getDataFolderPath()); // get the target of the AVD IAndroidTarget target = info.getTarget(); @@ -856,7 +942,8 @@ public class Main { mSdkLog.printf("---------\n"); } mSdkLog.printf(" Name: %s\n", info.getName() == null ? "--" : info.getName()); - mSdkLog.printf(" Path: %s\n", info.getPath() == null ? "--" : info.getPath()); + mSdkLog.printf(" Path: %s\n", + info.getDataFolderPath() == null ? "--" : info.getDataFolderPath()); String error = info.getErrorMessage(); mSdkLog.printf(" Error: %s\n", error == null ? "Uknown error" : error); @@ -923,8 +1010,7 @@ public class Main { if (paramFolderPath != null) { avdFolder = new File(paramFolderPath); } else { - avdFolder = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD, - avdName + AvdManager.AVD_FOLDER_EXTENSION); + avdFolder = AvdInfo.getDefaultAvdFolder(avdManager, avdName); } // Validate skin is either default (empty) or NNNxMMM or a valid skin name. @@ -981,15 +1067,20 @@ public class Main { oldAvdInfo = avdManager.getAvd(avdName, false /*validAvdOnly*/); } + // NOTE: need to update with command line processor selectivity + + String preferredAbi = SdkConstants.ABI_ARMEABI; @SuppressWarnings("unused") // newAvdInfo is never read, yet useful for debugging AvdInfo newAvdInfo = avdManager.createAvd(avdFolder, avdName, target, + preferredAbi, skin, mSdkCommandLine.getParamSdCard(), hardwareConfig, - removePrevious, mSdkCommandLine.getFlagSnapshot(), + removePrevious, + false, //edit existing mSdkLog); } catch (AndroidLocationException e) { @@ -1045,7 +1136,7 @@ public class Main { // check if paths are the same. Use File methods to account for OS idiosyncrasies. try { File f1 = new File(paramFolderPath).getCanonicalFile(); - File f2 = new File(info.getPath()).getCanonicalFile(); + File f2 = new File(info.getDataFolderPath()).getCanonicalFile(); if (f1.equals(f2)) { // same canonical path, so not actually a move paramFolderPath = null; @@ -1071,7 +1162,7 @@ public class Main { File originalFolder = new File( AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD, info.getName() + AvdManager.AVD_FOLDER_EXTENSION); - if (originalFolder.equals(info.getPath())) { + if (originalFolder.equals(info.getDataFolderPath())) { try { // The AVD is using the default data folder path based on the AVD name. // That folder needs to be adjusted to use the new name. @@ -1093,7 +1184,7 @@ public class Main { } File ini = info.getIniFile(); - if (ini.equals(AvdInfo.getIniFile(newName))) { + if (ini.equals(AvdInfo.getDefaultIniFile(avdManager, newName))) { errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath()); return; } diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java index 5bb6c4e..fb15cb5 100644 --- a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java +++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java @@ -38,47 +38,49 @@ class SdkCommandLine extends CommandLineProcessor { * or optional) for the given action. */ - public final static String VERB_LIST = "list"; - public final static String VERB_CREATE = "create"; - public final static String VERB_MOVE = "move"; - public final static String VERB_DELETE = "delete"; - public final static String VERB_UPDATE = "update"; - - public static final String OBJECT_SDK = "sdk"; - public static final String OBJECT_AVD = "avd"; - public static final String OBJECT_AVDS = "avds"; - public static final String OBJECT_TARGET = "target"; - public static final String OBJECT_TARGETS = "targets"; - public static final String OBJECT_PROJECT = "project"; - public static final String OBJECT_TEST_PROJECT = "test-project"; - public static final String OBJECT_LIB_PROJECT = "lib-project"; - public static final String OBJECT_EXPORT_PROJECT = "export-project"; - public static final String OBJECT_ADB = "adb"; - - public static final String ARG_ALIAS = "alias"; - public static final String ARG_ACTIVITY = "activity"; + public final static String VERB_LIST = "list"; //$NON-NLS-1$ + public final static String VERB_CREATE = "create"; //$NON-NLS-1$ + public final static String VERB_MOVE = "move"; //$NON-NLS-1$ + public final static String VERB_DELETE = "delete"; //$NON-NLS-1$ + public final static String VERB_UPDATE = "update"; //$NON-NLS-1$ + + public static final String OBJECT_SDK = "sdk"; //$NON-NLS-1$ + public static final String OBJECT_AVD = "avd"; //$NON-NLS-1$ + public static final String OBJECT_AVDS = "avds"; //$NON-NLS-1$ + public static final String OBJECT_TARGET = "target"; //$NON-NLS-1$ + public static final String OBJECT_TARGETS = "targets"; //$NON-NLS-1$ + public static final String OBJECT_PROJECT = "project"; //$NON-NLS-1$ + public static final String OBJECT_TEST_PROJECT = "test-project"; //$NON-NLS-1$ + public static final String OBJECT_LIB_PROJECT = "lib-project"; //$NON-NLS-1$ + public static final String OBJECT_EXPORT_PROJECT = "export-project"; //$NON-NLS-1$ + public static final String OBJECT_ADB = "adb"; //$NON-NLS-1$ + + public static final String ARG_ALIAS = "alias"; //$NON-NLS-1$ + public static final String ARG_ACTIVITY = "activity"; //$NON-NLS-1$ public static final String KEY_ACTIVITY = ARG_ACTIVITY; - public static final String KEY_PACKAGE = "package"; - public static final String KEY_MODE = "mode"; + public static final String KEY_PACKAGE = "package"; //$NON-NLS-1$ + public static final String KEY_MODE = "mode"; //$NON-NLS-1$ public static final String KEY_TARGET_ID = OBJECT_TARGET; - public static final String KEY_NAME = "name"; - public static final String KEY_LIBRARY = "library"; - public static final String KEY_PATH = "path"; - public static final String KEY_FILTER = "filter"; - public static final String KEY_SKIN = "skin"; - public static final String KEY_SDCARD = "sdcard"; - public static final String KEY_FORCE = "force"; - public static final String KEY_RENAME = "rename"; - public static final String KEY_SUBPROJECTS = "subprojects"; - public static final String KEY_MAIN_PROJECT = "main"; - public static final String KEY_NO_UI = "no-ui"; - public static final String KEY_NO_HTTPS = "no-https"; - public static final String KEY_PROXY_PORT = "proxy-port"; - public static final String KEY_PROXY_HOST = "proxy-host"; - public static final String KEY_DRY_MODE = "dry-mode"; - public static final String KEY_OBSOLETE = "obsolete"; - public static final String KEY_SNAPSHOT = "snapshot"; + public static final String KEY_NAME = "name"; //$NON-NLS-1$ + public static final String KEY_LIBRARY = "library"; //$NON-NLS-1$ + public static final String KEY_PATH = "path"; //$NON-NLS-1$ + public static final String KEY_FILTER = "filter"; //$NON-NLS-1$ + public static final String KEY_SKIN = "skin"; //$NON-NLS-1$ + public static final String KEY_SDCARD = "sdcard"; //$NON-NLS-1$ + public static final String KEY_FORCE = "force"; //$NON-NLS-1$ + public static final String KEY_RENAME = "rename"; //$NON-NLS-1$ + public static final String KEY_SUBPROJECTS = "subprojects"; //$NON-NLS-1$ + public static final String KEY_MAIN_PROJECT = "main"; //$NON-NLS-1$ + public static final String KEY_NO_UI = "no-ui"; //$NON-NLS-1$ + public static final String KEY_NO_HTTPS = "no-https"; //$NON-NLS-1$ + public static final String KEY_PROXY_PORT = "proxy-port"; //$NON-NLS-1$ + public static final String KEY_PROXY_HOST = "proxy-host"; //$NON-NLS-1$ + public static final String KEY_DRY_MODE = "dry-mode"; //$NON-NLS-1$ + public static final String KEY_OBSOLETE = "obsolete"; //$NON-NLS-1$ + public static final String KEY_SNAPSHOT = "snapshot"; //$NON-NLS-1$ + public static final String KEY_COMPACT = "compact"; //$NON-NLS-1$ + public static final String KEY_EOL_NULL = "null"; //$NON-NLS-1$ /** * Action definitions for SdkManager command line. @@ -104,6 +106,8 @@ class SdkCommandLine extends CommandLineProcessor { { VERB_LIST, OBJECT_TARGET, "Lists existing targets.", OBJECT_TARGETS }, + { VERB_LIST, OBJECT_SDK, + "Lists remote SDK repository." }, { VERB_CREATE, OBJECT_AVD, "Creates a new Android Virtual Device." }, @@ -147,92 +151,139 @@ class SdkCommandLine extends CommandLineProcessor { // The following defines the parameters of the actions defined in mAction. + // --- list avds --- + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_AVD, "c", KEY_COMPACT, //$NON-NLS-1$ + "Compact output (suitable for scripts)", false); + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_AVD, "0", KEY_EOL_NULL, //$NON-NLS-1$ + "Terminates lines with \\0 instead of \\n (e.g. for xargs -0). Only used by --" + KEY_COMPACT + ".", + false); + + // --- list targets --- + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_TARGET, "c", KEY_COMPACT, //$NON-NLS-1$ + "Compact output (suitable for scripts)", false); + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_TARGET, "0", KEY_EOL_NULL, //$NON-NLS-1$ + "Terminates lines with \\0 instead of \\n (e.g. for xargs -0) Only used by --" + KEY_COMPACT + ".", + false); + // --- create avd --- define(Mode.STRING, false, - VERB_CREATE, OBJECT_AVD, "p", KEY_PATH, + VERB_CREATE, OBJECT_AVD, "p", KEY_PATH, //$NON-NLS-1$ "Directory where the new AVD will be created", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_AVD, "n", KEY_NAME, + VERB_CREATE, OBJECT_AVD, "n", KEY_NAME, //$NON-NLS-1$ "Name of the new AVD", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID, + VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID, //$NON-NLS-1$ "Target ID of the new AVD", null); define(Mode.STRING, false, - VERB_CREATE, OBJECT_AVD, "s", KEY_SKIN, + VERB_CREATE, OBJECT_AVD, "s", KEY_SKIN, //$NON-NLS-1$ "Skin for the new AVD", null); define(Mode.STRING, false, - VERB_CREATE, OBJECT_AVD, "c", KEY_SDCARD, + VERB_CREATE, OBJECT_AVD, "c", KEY_SDCARD, //$NON-NLS-1$ "Path to a shared SD card image, or size of a new sdcard for the new AVD", null); define(Mode.BOOLEAN, false, - VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE, + VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE, //$NON-NLS-1$ "Forces creation (overwrites an existing AVD)", false); define(Mode.BOOLEAN, false, - VERB_CREATE, OBJECT_AVD, "a", KEY_SNAPSHOT, + VERB_CREATE, OBJECT_AVD, "a", KEY_SNAPSHOT, //$NON-NLS-1$ "Place a snapshots file in the AVD, to enable persistence.", false); // --- delete avd --- define(Mode.STRING, true, - VERB_DELETE, OBJECT_AVD, "n", KEY_NAME, + VERB_DELETE, OBJECT_AVD, "n", KEY_NAME, //$NON-NLS-1$ "Name of the AVD to delete", null); // --- move avd --- define(Mode.STRING, true, - VERB_MOVE, OBJECT_AVD, "n", KEY_NAME, + VERB_MOVE, OBJECT_AVD, "n", KEY_NAME, //$NON-NLS-1$ "Name of the AVD to move or rename", null); define(Mode.STRING, false, - VERB_MOVE, OBJECT_AVD, "r", KEY_RENAME, + VERB_MOVE, OBJECT_AVD, "r", KEY_RENAME, //$NON-NLS-1$ "New name of the AVD", null); define(Mode.STRING, false, - VERB_MOVE, OBJECT_AVD, "p", KEY_PATH, + VERB_MOVE, OBJECT_AVD, "p", KEY_PATH, //$NON-NLS-1$ "Path to the AVD's new directory", null); // --- update avd --- define(Mode.STRING, true, - VERB_UPDATE, OBJECT_AVD, "n", KEY_NAME, + VERB_UPDATE, OBJECT_AVD, "n", KEY_NAME, //$NON-NLS-1$ "Name of the AVD to update", null); + // --- list sdk --- + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_SDK, "u", KEY_NO_UI, //$NON-NLS-1$ + "Displays list result on console (no GUI)", true); + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_SDK, "s", KEY_NO_HTTPS, //$NON-NLS-1$ + "Uses HTTP instead of HTTPS (the default) for downloads", false); + + define(Mode.STRING, false, + VERB_LIST, OBJECT_SDK, "", KEY_PROXY_PORT, //$NON-NLS-1$ + "HTTP/HTTPS proxy port (overrides settings if defined)", + null); + + define(Mode.STRING, false, + VERB_LIST, OBJECT_SDK, "", KEY_PROXY_HOST, //$NON-NLS-1$ + "HTTP/HTTPS proxy host (overrides settings if defined)", + null); + + define(Mode.BOOLEAN, false, + VERB_LIST, OBJECT_SDK, "o", KEY_OBSOLETE, //$NON-NLS-1$ + "Installs obsolete packages", + false); + // --- update sdk --- define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_SDK, "u", KEY_NO_UI, + VERB_UPDATE, OBJECT_SDK, "u", KEY_NO_UI, //$NON-NLS-1$ "Updates from command-line (does not display the GUI)", false); define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_SDK, "s", KEY_NO_HTTPS, + VERB_UPDATE, OBJECT_SDK, "s", KEY_NO_HTTPS, //$NON-NLS-1$ "Uses HTTP instead of HTTPS (the default) for downloads", false); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_SDK, "", KEY_PROXY_PORT, + VERB_UPDATE, OBJECT_SDK, "", KEY_PROXY_PORT, //$NON-NLS-1$ "HTTP/HTTPS proxy port (overrides settings if defined)", null); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_SDK, "", KEY_PROXY_HOST, + VERB_UPDATE, OBJECT_SDK, "", KEY_PROXY_HOST, //$NON-NLS-1$ "HTTP/HTTPS proxy host (overrides settings if defined)", null); define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_SDK, "f", KEY_FORCE, + VERB_UPDATE, OBJECT_SDK, "f", KEY_FORCE, //$NON-NLS-1$ "Forces replacement of a package or its parts, even if something has been modified", false); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_SDK, "t", KEY_FILTER, + VERB_UPDATE, OBJECT_SDK, "t", KEY_FILTER, //$NON-NLS-1$ "A filter that limits the update to the specified types of packages in the form of\n" + "a comma-separated list of " + Arrays.toString(SdkRepoConstants.NODES), null); define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_SDK, "o", KEY_OBSOLETE, + VERB_UPDATE, OBJECT_SDK, "o", KEY_OBSOLETE, //$NON-NLS-1$ "Installs obsolete packages", false); define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_SDK, "n", KEY_DRY_MODE, + VERB_UPDATE, OBJECT_SDK, "n", KEY_DRY_MODE, //$NON-NLS-1$ "Simulates the update but does not download or install anything", false); @@ -242,7 +293,7 @@ class SdkCommandLine extends CommandLineProcessor { This currently does not work, the alias build rules need to be fixed. define(Mode.ENUM, true, - VERB_CREATE, OBJECT_PROJECT, "m", KEY_MODE, + VERB_CREATE, OBJECT_PROJECT, "m", KEY_MODE, //$NON-NLS-1$ "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS }); */ define(Mode.STRING, true, @@ -250,45 +301,44 @@ class SdkCommandLine extends CommandLineProcessor { "p", KEY_PATH, "The new project's directory", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID, + VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID, //$NON-NLS-1$ "Target ID of the new project", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_PROJECT, "k", KEY_PACKAGE, + VERB_CREATE, OBJECT_PROJECT, "k", KEY_PACKAGE, //$NON-NLS-1$ "Android package name for the application", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_PROJECT, "a", KEY_ACTIVITY, + VERB_CREATE, OBJECT_PROJECT, "a", KEY_ACTIVITY, //$NON-NLS-1$ "Name of the default Activity that is created", null); define(Mode.STRING, false, - VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME, + VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME, //$NON-NLS-1$ "Project name", null); // --- create test-project --- define(Mode.STRING, true, - VERB_CREATE, OBJECT_TEST_PROJECT, - "p", KEY_PATH, + VERB_CREATE, OBJECT_TEST_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "The new project's directory", null); define(Mode.STRING, false, - VERB_CREATE, OBJECT_TEST_PROJECT, "n", KEY_NAME, + VERB_CREATE, OBJECT_TEST_PROJECT, "n", KEY_NAME, //$NON-NLS-1$ "Project name", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_TEST_PROJECT, "m", KEY_MAIN_PROJECT, - "Path to directory of the app under test, relative to the test project directory", null); + VERB_CREATE, OBJECT_TEST_PROJECT, "m", KEY_MAIN_PROJECT, //$NON-NLS-1$ + "Path to directory of the app under test, relative to the test project directory", + null); // --- create lib-project --- define(Mode.STRING, true, - VERB_CREATE, OBJECT_LIB_PROJECT, - "p", KEY_PATH, + VERB_CREATE, OBJECT_LIB_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "The new project's directory", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_LIB_PROJECT, "t", KEY_TARGET_ID, + VERB_CREATE, OBJECT_LIB_PROJECT, "t", KEY_TARGET_ID, //$NON-NLS-1$ "Target ID of the new project", null); define(Mode.STRING, false, - VERB_CREATE, OBJECT_LIB_PROJECT, "n", KEY_NAME, + VERB_CREATE, OBJECT_LIB_PROJECT, "n", KEY_NAME, //$NON-NLS-1$ "Project name", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_LIB_PROJECT, "k", KEY_PACKAGE, + VERB_CREATE, OBJECT_LIB_PROJECT, "k", KEY_PACKAGE, //$NON-NLS-1$ "Android package name for the library", null); // --- create export-project --- @@ -296,74 +346,63 @@ class SdkCommandLine extends CommandLineProcessor { * disabled until the feature is officially supported. define(Mode.STRING, true, - VERB_CREATE, OBJECT_EXPORT_PROJECT, - "p", KEY_PATH, + VERB_CREATE, OBJECT_EXPORT_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "Location path of new project", null); define(Mode.STRING, false, - VERB_CREATE, OBJECT_EXPORT_PROJECT, "n", KEY_NAME, + VERB_CREATE, OBJECT_EXPORT_PROJECT, "n", KEY_NAME, //$NON-NLS-1$ "Project name", null); define(Mode.STRING, true, - VERB_CREATE, OBJECT_EXPORT_PROJECT, "k", KEY_PACKAGE, + VERB_CREATE, OBJECT_EXPORT_PROJECT, "k", KEY_PACKAGE, //$NON-NLS-1$ "Package name", null); */ // --- update project --- define(Mode.STRING, true, - VERB_UPDATE, OBJECT_PROJECT, - "p", KEY_PATH, + VERB_UPDATE, OBJECT_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "The project's directory", null); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_PROJECT, - "t", KEY_TARGET_ID, + VERB_UPDATE, OBJECT_PROJECT, "t", KEY_TARGET_ID, //$NON-NLS-1$ "Target ID to set for the project", null); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_PROJECT, - "n", KEY_NAME, + VERB_UPDATE, OBJECT_PROJECT, "n", KEY_NAME, //$NON-NLS-1$ "Project name", null); define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_PROJECT, - "s", KEY_SUBPROJECTS, + VERB_UPDATE, OBJECT_PROJECT, "s", KEY_SUBPROJECTS, //$NON-NLS-1$ "Also updates any projects in sub-folders, such as test projects.", false); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_PROJECT, - "l", KEY_LIBRARY, - "Directory of an Android library to add, relative to this project's directory", null); + VERB_UPDATE, OBJECT_PROJECT, "l", KEY_LIBRARY, //$NON-NLS-1$ + "Directory of an Android library to add, relative to this project's directory", + null); // --- update test project --- define(Mode.STRING, true, - VERB_UPDATE, OBJECT_TEST_PROJECT, - "p", KEY_PATH, + VERB_UPDATE, OBJECT_TEST_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "The project's directory", null); define(Mode.STRING, true, - VERB_UPDATE, OBJECT_TEST_PROJECT, - "m", KEY_MAIN_PROJECT, + VERB_UPDATE, OBJECT_TEST_PROJECT, "m", KEY_MAIN_PROJECT, //$NON-NLS-1$ "Directory of the app under test, relative to the test project directory", null); // --- update lib project --- define(Mode.STRING, true, - VERB_UPDATE, OBJECT_LIB_PROJECT, - "p", KEY_PATH, + VERB_UPDATE, OBJECT_LIB_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "The project's directory", null); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_LIB_PROJECT, - "t", KEY_TARGET_ID, + VERB_UPDATE, OBJECT_LIB_PROJECT, "t", KEY_TARGET_ID, //$NON-NLS-1$ "Target ID to set for the project", null); // --- update export project --- /* * disabled until the feature is officially supported. define(Mode.STRING, true, - VERB_UPDATE, OBJECT_EXPORT_PROJECT, - "p", KEY_PATH, + VERB_UPDATE, OBJECT_EXPORT_PROJECT, "p", KEY_PATH, //$NON-NLS-1$ "Location path of the project", null); define(Mode.STRING, false, - VERB_UPDATE, OBJECT_EXPORT_PROJECT, - "n", KEY_NAME, + VERB_UPDATE, OBJECT_EXPORT_PROJECT, "n", KEY_NAME, //$NON-NLS-1$ "Project name", null); define(Mode.BOOLEAN, false, - VERB_UPDATE, OBJECT_EXPORT_PROJECT, "f", KEY_FORCE, + VERB_UPDATE, OBJECT_EXPORT_PROJECT, "f", KEY_FORCE, //$NON-NLS-1$ "Force replacing the build.xml file", false); */ } @@ -464,9 +503,9 @@ class SdkCommandLine extends CommandLineProcessor { // -- some helpers for update sdk flags - /** Helper to retrieve the --force flag. */ - public boolean getFlagNoUI() { - return ((Boolean) getValue(null, null, KEY_NO_UI)).booleanValue(); + /** Helper to retrieve the --no-ui flag. */ + public boolean getFlagNoUI(String verb) { + return ((Boolean) getValue(verb, null, KEY_NO_UI)).booleanValue(); } /** Helper to retrieve the --no-https flag. */ @@ -490,12 +529,24 @@ class SdkCommandLine extends CommandLineProcessor { } /** Helper to retrieve the --proxy-host value. */ - public String getProxyHost() { + public String getParamProxyHost() { return ((String) getValue(null, null, KEY_PROXY_HOST)); } /** Helper to retrieve the --proxy-port value. */ - public String getProxyPort() { + public String getParamProxyPort() { return ((String) getValue(null, null, KEY_PROXY_PORT)); } + + // -- some helpers for list avds and list targets flags + + /** Helper to retrieve the --compact value. */ + public boolean getFlagCompact() { + return ((Boolean) getValue(null, null, KEY_COMPACT)).booleanValue(); + } + + /** Helper to retrieve the --null value. */ + public boolean getFlagEolNull() { + return ((Boolean) getValue(null, null, KEY_EOL_NULL)).booleanValue(); + } } diff --git a/sdkmanager/app/src/com/android/sdkmanager/internal/repository/AboutPage.java b/sdkmanager/app/src/com/android/sdkmanager/internal/repository/AboutPage.java index cb2f981..4c72e1e 100755 --- a/sdkmanager/app/src/com/android/sdkmanager/internal/repository/AboutPage.java +++ b/sdkmanager/app/src/com/android/sdkmanager/internal/repository/AboutPage.java @@ -70,7 +70,7 @@ public class AboutPage extends Composite { "Revision %1$s\n" +
"Add-on XML Schema #%2$d\n" +
"Repository XML Schema #%3$d\n" +
- "Copyright (C) 2009-2010 The Android Open Source Project.",
+ "Copyright (C) 2009-2011 The Android Open Source Project.",
getRevision(),
SdkAddonConstants.NS_LATEST_VERSION,
SdkRepoConstants.NS_LATEST_VERSION));
diff --git a/androidprefs/src/Android.mk b/sdkmanager/app/tests/Android.mk index ddc0aa6..4f67370 100644 --- a/androidprefs/src/Android.mk +++ b/sdkmanager/app/tests/Android.mk @@ -1,5 +1,4 @@ -# -# Copyright (C) 2008 The Android Open Source Project +# 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. @@ -12,13 +11,18 @@ # 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-subdir-java-files) -LOCAL_MODULE := androidprefs +LOCAL_MODULE := sdkmanager-tests +LOCAL_MODULE_TAGS := optional -include $(BUILD_HOST_JAVA_LIBRARY) +LOCAL_JAVA_LIBRARIES := sdkmanager sdklib-tests junit +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java b/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java index 4ae107c..a5a8289 100644 --- a/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java +++ b/sdkmanager/app/tests/com/android/sdkmanager/AvdManagerTest.java @@ -16,59 +16,57 @@ package com.android.sdkmanager; -import static java.io.File.createTempFile; - +import com.android.io.FileWrapper; import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkManager; -import com.android.sdklib.internal.avd.AvdManager; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.project.ProjectProperties; -import com.android.sdklib.io.FileWrapper; -import com.android.sdklib.mock.MockLog; import java.io.File; import java.util.Map; -import junit.framework.TestCase; - -public class AvdManagerTest extends TestCase { +public class AvdManagerTest extends SdkManagerTestCase { - private AvdManager mAvdManager; - private SdkManager mSdkManager; - private MockLog mLog; - private File mFakeSdk; - private File mAvdFolder; private IAndroidTarget mTarget; + private File mAvdFolder; @Override public void setUp() throws Exception { - mLog = new MockLog(); - mFakeSdk = SdkManagerTestUtil.makeFakeSdk(createTempFile(this.getClass().getSimpleName(), null)); - mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog); - assertNotNull("sdkManager location was invalid", mSdkManager); + super.setUp(); - mAvdManager = new AvdManager(mSdkManager, mLog); - mAvdFolder = new File(mFakeSdk, "avdData"); - mTarget = mSdkManager.getTargets()[0]; + mTarget = getSdkManager().getTargets()[0]; + mAvdFolder = AvdInfo.getDefaultAvdFolder(getAvdManager(), getName()); } @Override public void tearDown() throws Exception { - SdkManagerTestUtil.deleteDir(mFakeSdk); + super.tearDown(); } public void testCreateAvdWithoutSnapshot() { - mAvdManager.createAvd( - mAvdFolder, this.getName(), mTarget, null, null, null, false, false, mLog); - assertEquals("[P Created AVD '" + this.getName() + "' based on Android 0.0\n]", - mLog.toString()); + getAvdManager().createAvd( + mAvdFolder, + this.getName(), + mTarget, + SdkConstants.ABI_ARMEABI, + null, // skinName + null, // sdName + null, // properties + false, // createSnapshot + false, // removePrevious + false, // editExisting + getLog()); + + assertEquals("[P Created AVD '" + this.getName() + "' based on Android 0.0, ARM (armeabi) processor\n]", + getLog().toString()); assertTrue("Expected config.ini in " + mAvdFolder, new File(mAvdFolder, "config.ini").exists()); Map<String, String> map = ProjectProperties.parsePropertyFile( - new FileWrapper(mAvdFolder, "config.ini"), mLog); + new FileWrapper(mAvdFolder, "config.ini"), getLog()); assertEquals("HVGA", map.get("skin.name")); - assertEquals("platforms/v0_0/skins/HVGA", map.get("skin.path")); - assertEquals("platforms/v0_0/images/", map.get("image.sysdir.1")); + assertEquals("platforms/v0_0/skins/HVGA", map.get("skin.path").replace(File.separatorChar, '/')); + assertEquals("platforms/v0_0/images/", map.get("image.sysdir.1").replace(File.separatorChar, '/')); assertEquals(null, map.get("snapshot.present")); assertTrue("Expected userdata.img in " + mAvdFolder, new File(mAvdFolder, "userdata.img").exists()); @@ -77,15 +75,26 @@ public class AvdManagerTest extends TestCase { } public void testCreateAvdWithSnapshot() { - mAvdManager.createAvd( - mAvdFolder, this.getName(), mTarget, null, null, null, false, true, mLog); - assertEquals("[P Created AVD '" + this.getName() + "' based on Android 0.0\n]", - mLog.toString()); + getAvdManager().createAvd( + mAvdFolder, + this.getName(), + mTarget, + SdkConstants.ABI_ARMEABI, + null, // skinName + null, // sdName + null, // properties + true, // createSnapshot + false, // removePrevious + false, // editExisting + getLog()); + + assertEquals("[P Created AVD '" + this.getName() + "' based on Android 0.0, ARM (armeabi) processor\n]", + getLog().toString()); assertTrue("Expected snapshots.img in " + mAvdFolder, new File(mAvdFolder, "snapshots.img").exists()); Map<String, String> map = ProjectProperties.parsePropertyFile( - new FileWrapper(mAvdFolder, "config.ini"), mLog); + new FileWrapper(mAvdFolder, "config.ini"), getLog()); assertEquals("true", map.get("snapshot.present")); } } diff --git a/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java b/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java index 29516e3..4a17e32 100644 --- a/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java +++ b/sdkmanager/app/tests/com/android/sdkmanager/MainTest.java @@ -17,60 +17,63 @@ package com.android.sdkmanager; -import static java.io.File.createTempFile; - import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkManager; -import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.mock.MockLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.avd.AvdInfo; +import com.android.sdklib.repository.SdkAddonConstants; +import com.android.sdklib.repository.SdkRepoConstants; +import com.android.util.Pair; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; -import junit.framework.TestCase; - -public class MainTest extends TestCase { +public class MainTest extends SdkManagerTestCase { - private File mFakeSdk; - private MockLog mLog; - private SdkManager mSdkManager; - private AvdManager mAvdManager; - private File mAvdFolder; private IAndroidTarget mTarget; - private File fakeSdkDir; + private File mAvdFolder; @Override public void setUp() throws Exception { - mLog = new MockLog(); - fakeSdkDir = createTempFile(this.getClass().getSimpleName() + "_" + this.getName(), null); - mFakeSdk = SdkManagerTestUtil.makeFakeSdk(fakeSdkDir); - mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog); - assertNotNull("sdkManager location was invalid", mSdkManager); - - mAvdManager = new AvdManager(mSdkManager, mLog); - mAvdFolder = new File(mFakeSdk, "avdData"); - mTarget = mSdkManager.getTargets()[0]; + super.setUp(); + + mTarget = getSdkManager().getTargets()[0]; + mAvdFolder = AvdInfo.getDefaultAvdFolder(getAvdManager(), getName()); } @Override public void tearDown() throws Exception { - SdkManagerTestUtil.deleteDir(mFakeSdk); + super.tearDown(); } - public void txestDisplayEmptyAvdList() { + public void testDisplayEmptyAvdList() { Main main = new Main(); - main.setLogger(mLog); - mLog.clear(); - main.displayAvdList(mAvdManager); - assertEquals("P Available Android Virtual Devices:\n", mLog.toString()); + main.setLogger(getLog()); + getLog().clear(); + main.displayAvdList(getAvdManager()); + assertEquals("[P Available Android Virtual Devices:\n]", getLog().toString()); } public void testDisplayAvdListOfOneNonSnapshot() { Main main = new Main(); - main.setLogger(mLog); - mAvdManager.createAvd( - mAvdFolder, this.getName(), mTarget, null, null, null, false, false, mLog); - mLog.clear(); - main.displayAvdList(mAvdManager); + main.setLogger(getLog()); + getAvdManager().createAvd( + mAvdFolder, + this.getName(), + mTarget, + SdkConstants.ABI_ARMEABI, + null, // skinName + null, // sdName + null, // properties + false, // createSnapshot + false, // removePrevious + false, // editExisting + getLog()); + + getLog().clear(); + main.displayAvdList(getAvdManager()); assertEquals( "[P Available Android Virtual Devices:\n" + ", P Name: " + this.getName() + "\n" @@ -78,16 +81,28 @@ public class MainTest extends TestCase { + ", P Target: Android 0.0 (API level 0)\n" + ", P Skin: HVGA\n" + "]", - mLog.toString()); + getLog().toString()); } public void testDisplayAvdListOfOneSnapshot() { Main main = new Main(); - main.setLogger(mLog); - mAvdManager.createAvd( - mAvdFolder, this.getName(), mTarget, null, null, null, false, true, mLog); - mLog.clear(); - main.displayAvdList(mAvdManager); + main.setLogger(getLog()); + + getAvdManager().createAvd( + mAvdFolder, + this.getName(), + mTarget, + SdkConstants.ABI_ARMEABI, + null, // skinName + null, // sdName + null, // properties + true, // createSnapshot + false, // removePrevious + false, // editExisting + getLog()); + + getLog().clear(); + main.displayAvdList(getAvdManager()); assertEquals( "[P Available Android Virtual Devices:\n" + ", P Name: " + this.getName() + "\n" @@ -96,6 +111,83 @@ public class MainTest extends TestCase { + ", P Skin: HVGA\n" + ", P Snapshot: true\n" + "]", - mLog.toString()); + getLog().toString()); + } + + public void testCheckFilterValues() { + // These are the values we expect checkFilterValues() to match. + String[] expectedValues = { + "platform", + "tool", + "platform-tool", + "doc", + "sample", + "add-on", + "extra" + }; + + Set<String> expectedSet = new TreeSet<String>(Arrays.asList(expectedValues)); + + // First check the values are actually defined in the proper arrays + // in the Sdk*Constants.NODES + for (String node : SdkRepoConstants.NODES) { + assertTrue( + String.format( + "Error: value '%1$s' from SdkRepoConstants.NODES should be used in unit-test", + node), + expectedSet.contains(node)); + } + for (String node : SdkAddonConstants.NODES) { + assertTrue( + String.format( + "Error: value '%1$s' from SdkAddonConstants.NODES should be used in unit-test", + node), + expectedSet.contains(node)); + } + + // Now check none of these values are NOT present in the NODES arrays + for (String node : SdkRepoConstants.NODES) { + expectedSet.remove(node); + } + for (String node : SdkAddonConstants.NODES) { + expectedSet.remove(node); + } + assertTrue( + String.format( + "Error: values %1$s are missing from Sdk[Repo|Addons]Constants.NODES", + Arrays.toString(expectedSet.toArray())), + expectedSet.isEmpty()); + + // We're done with expectedSet now + expectedSet = null; + + // Finally check that checkFilterValues accepts all these values, one by one. + Main main = new Main(); + main.setLogger(getLog()); + + for (int step = 0; step < 3; step++) { + for (String value : expectedValues) { + switch(step) { + // step 0: use value as-is + case 1: + // add some whitespace before and after + value = " " + value + " "; + break; + case 2: + // same with some empty arguments that should get ignored + value = " ," + value + " , "; + break; + } + + Pair<String, ArrayList<String>> result = main.checkFilterValues(value); + assertNull( + String.format("Expected error to be null for value '%1$s', got: %2$s", + value, result.getFirst()), + result.getFirst()); + assertEquals( + String.format("[%1$s]", value.replace(',', ' ').trim()), + Arrays.toString(result.getSecond().toArray())); + } + } } } diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestCase.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestCase.java new file mode 100755 index 0000000..9fdd852 --- /dev/null +++ b/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestCase.java @@ -0,0 +1,182 @@ +/* + * 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.sdkmanager; + + +import com.android.prefs.AndroidLocation; +import com.android.prefs.AndroidLocation.AndroidLocationException; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.android.sdklib.internal.avd.AvdManager; +import com.android.sdklib.mock.MockLog; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import junit.framework.TestCase; + +/** + * Test case that allocates a temporary SDK, a temporary AVD base folder + * with an SdkManager and an AvdManager that points to them. + */ +public abstract class SdkManagerTestCase extends TestCase { + + private File mFakeSdk; + private MockLog mLog; + private SdkManager mSdkManager; + private TmpAvdManager mAvdManager; + + /** Returns the {@link MockLog} for this test case. */ + public MockLog getLog() { + return mLog; + } + + /** Returns the {@link SdkManager} for this test case. */ + public SdkManager getSdkManager() { + return mSdkManager; + } + + /** Returns the {@link AvdManager} for this test case. */ + public TmpAvdManager getAvdManager() { + return mAvdManager; + } + + /** + * Sets up a {@link MockLog}, a fake SDK in a temporary directory + * and an AVD Manager pointing to an initially-empty AVD directory. + */ + @Override + public void setUp() throws Exception { + mLog = new MockLog(); + mFakeSdk = makeFakeSdk(); + mSdkManager = SdkManager.createManager(mFakeSdk.getAbsolutePath(), mLog); + assertNotNull("SdkManager location was invalid", mSdkManager); + + mAvdManager = new TmpAvdManager(mSdkManager, mLog); + } + + /** + * Removes the temporary SDK and AVD directories. + */ + @Override + public void tearDown() throws Exception { + deleteDir(mFakeSdk); + } + + /** + * An {@link AvdManager} that uses a temporary directory + * located <em>inside</em> the SDK directory for testing. + * The AVD list should be initially empty. + */ + protected static class TmpAvdManager extends AvdManager { + + /* + * Implementation detail: + * - When the super.AvdManager constructor is invoked, it will invoke + * the buildAvdFilesList() to fill the initial AVD list, which will in + * turn call getBaseAvdFolder(). + * - That's why mTmpAvdRoot is initialized in getAvdRoot() rather than + * in the constructor, since we can't initialize fields before the super() + * call. + */ + + /** + * AVD Root, initialized "lazily" when the AVD root is first requested. + */ + private File mTmpAvdRoot; + + public TmpAvdManager(SdkManager sdkManager, ISdkLog log) throws AndroidLocationException { + super(sdkManager, log); + } + + @Override + public String getBaseAvdFolder() throws AndroidLocationException { + if (mTmpAvdRoot == null) { + mTmpAvdRoot = new File(getSdkManager().getLocation(), "tmp_avds"); + mTmpAvdRoot.mkdirs(); + } + return mTmpAvdRoot.getAbsolutePath(); + } + } + + /** + * Build enough of a skeleton SDK to make the tests pass. + * <p/> + * Ideally this wouldn't touch the file system but the current + * structure of the SdkManager and AvdManager makes this difficult. + * + * @return Path to the temporary SDK root + * @throws IOException + */ + private File makeFakeSdk() throws IOException { + + File tmpFile = File.createTempFile( + this.getClass().getSimpleName() + '_' + this.getName(), null); + tmpFile.delete(); + tmpFile.mkdirs(); + + AndroidLocation.resetFolder(); + System.setProperty("user.home", tmpFile.getAbsolutePath()); + File addonsDir = new File(tmpFile, SdkConstants.FD_ADDONS); + addonsDir.mkdir(); + File toolsLibEmuDir = new File(tmpFile, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator"); + toolsLibEmuDir.mkdirs(); + new File(toolsLibEmuDir, "snapshots.img").createNewFile(); + File platformsDir = new File(tmpFile, SdkConstants.FD_PLATFORMS); + + // Creating a fake target here on down + File targetDir = new File(platformsDir, "v0_0"); + targetDir.mkdirs(); + new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile(); + new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile(); + new File(targetDir, SdkConstants.FN_SOURCE_PROP).createNewFile(); + File buildProp = new File(targetDir, SdkConstants.FN_BUILD_PROP); + FileWriter out = new FileWriter(buildProp); + out.write(SdkManager.PROP_VERSION_RELEASE + "=0.0\n"); + out.write(SdkManager.PROP_VERSION_SDK + "=0\n"); + out.write(SdkManager.PROP_VERSION_CODENAME + "=REL\n"); + out.close(); + File imagesDir = new File(targetDir, "images"); + imagesDir.mkdirs(); + new File(imagesDir, "userdata.img").createNewFile(); + File skinsDir = new File(targetDir, "skins"); + File hvgaDir = new File(skinsDir, "HVGA"); + hvgaDir.mkdirs(); + return tmpFile; + } + + /** + * Recursive delete directory. Mostly for fake SDKs. + * + * @param root directory to delete + */ + private void deleteDir(File root) { + if (root.exists()) { + for (File file : root.listFiles()) { + if (file.isDirectory()) { + deleteDir(file); + } else { + file.delete(); + } + } + root.delete(); + } + } + +} diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestUtil.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestUtil.java deleted file mode 100644 index 96efb5c..0000000 --- a/sdkmanager/app/tests/com/android/sdkmanager/SdkManagerTestUtil.java +++ /dev/null @@ -1,88 +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.sdkmanager; - -import com.android.prefs.AndroidLocation; -import com.android.sdklib.SdkConstants; -import com.android.sdklib.SdkManager; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -public class SdkManagerTestUtil { - /** - * Build enough of a skeleton SDK to make the tests pass. - *<p> - * Ideally this wouldn't touch the file system, but I'm not inclined to - * fiddle around with mock file systems just at the moment. - * - * @return an sdk manager to a fake sdk - * @throws IOException - */ - public static File makeFakeSdk(File fakeSdk) throws IOException { - fakeSdk.delete(); - fakeSdk.mkdirs(); - AndroidLocation.resetFolder(); - System.setProperty("user.home", fakeSdk.getAbsolutePath()); - File addonsDir = new File(fakeSdk, SdkConstants.FD_ADDONS); - addonsDir.mkdir(); - File toolsLibEmuDir = new File(fakeSdk, SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + "emulator"); - toolsLibEmuDir.mkdirs(); - new File(toolsLibEmuDir, "snapshots.img").createNewFile(); - File platformsDir = new File(fakeSdk, SdkConstants.FD_PLATFORMS); - - // Creating a fake target here on down - File targetDir = new File(platformsDir, "v0_0"); - targetDir.mkdirs(); - new File(targetDir, SdkConstants.FN_FRAMEWORK_LIBRARY).createNewFile(); - new File(targetDir, SdkConstants.FN_FRAMEWORK_AIDL).createNewFile(); - new File(targetDir, SdkConstants.FN_SOURCE_PROP).createNewFile(); - File buildProp = new File(targetDir, SdkConstants.FN_BUILD_PROP); - FileWriter out = new FileWriter(buildProp); - out.write(SdkManager.PROP_VERSION_RELEASE + "=0.0\n"); - out.write(SdkManager.PROP_VERSION_SDK + "=0\n"); - out.write(SdkManager.PROP_VERSION_CODENAME + "=REL\n"); - out.close(); - File imagesDir = new File(targetDir, "images"); - imagesDir.mkdirs(); - new File(imagesDir, "userdata.img").createNewFile(); - File skinsDir = new File(targetDir, "skins"); - File hvgaDir = new File(skinsDir, "HVGA"); - hvgaDir.mkdirs(); - return fakeSdk; - } - - /** - * Recursive delete directory. Mostly for fake SDKs. - * - * @param root directory to delete - */ - public static void deleteDir(File root) { - if (root.exists()) { - for (File file : root.listFiles()) { - if (file.isDirectory()) { - deleteDir(file); - } else { - file.delete(); - } - } - root.delete(); - } - } - -} diff --git a/sdkmanager/libs/sdklib/.classpath b/sdkmanager/libs/sdklib/.classpath index 174a804..7cabaa0 100644 --- a/sdkmanager/libs/sdklib/.classpath +++ b/sdkmanager/libs/sdklib/.classpath @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> - <classpathentry kind="src" path="tests"/> + <classpathentry kind="src" path="tests/src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/commons-compress/commons-compress-1.0.jar"/> <classpathentry combineaccessrules="false" kind="src" path="/common"/> <classpathentry kind="output" path="bin"/> -</classpath> +</classpath>
\ No newline at end of file diff --git a/sdkmanager/libs/sdklib/.settings/org.eclipse.jdt.core.prefs b/sdkmanager/libs/sdklib/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..cba2e98 --- /dev/null +++ b/sdkmanager/libs/sdklib/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 16 15:10:56 PDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/sdkmanager/libs/sdklib/Android.mk b/sdkmanager/libs/sdklib/Android.mk index 509c573..7ed009c 100644 --- a/sdkmanager/libs/sdklib/Android.mk +++ b/sdkmanager/libs/sdklib/Android.mk @@ -13,5 +13,27 @@ # See the License for the specific language governing permissions and # limitations under the License. # -SDKLIB_LOCAL_DIR := $(call my-dir) -include $(SDKLIB_LOCAL_DIR)/src/Android.mk +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_JAR_MANIFEST := manifest.txt + +# IMPORTANT: if you add a new dependency here, please make sure +# to also check the following files: +# sdkmanager/sdklib/manifest.txt +# sdkmanager/app/etc/android.bat +LOCAL_JAVA_LIBRARIES := \ + androidprefs \ + common \ + commons-compress-1.0 + +LOCAL_MODULE := sdklib + +include $(BUILD_HOST_JAVA_LIBRARY) + + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/sdkmanager/libs/sdklib/NOTICE b/sdkmanager/libs/sdklib/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/sdkmanager/libs/sdklib/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java index f3da39c..866d5b6 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/AddOnTarget.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.ArrayList; /** * Represents an add-on target in the SDK. @@ -67,6 +68,7 @@ final class AddOnTarget implements IAndroidTarget { private final String mLocation; private final PlatformTarget mBasePlatform; private final String mName; + private String[] mAbis; private final String mVendor; private final int mRevision; private final String mDescription; @@ -74,6 +76,7 @@ final class AddOnTarget implements IAndroidTarget { private String mDefaultSkin; private IOptionalLibrary[] mLibraries; private int mVendorId = NO_USB_ID; + private boolean mAbiCompatibilityMode; /** * Creates a new add-on @@ -82,12 +85,13 @@ final class AddOnTarget implements IAndroidTarget { * @param vendor the vendor name of the add-on * @param revision the revision of the add-on * @param description the add-on description + * @param abis list of supported abis * @param libMap A map containing the optional libraries. The map key is the fully-qualified * library name. The value is a 2 string array with the .jar filename, and the description. * @param basePlatform the platform the add-on is extending. */ AddOnTarget(String location, String name, String vendor, int revision, String description, - Map<String, String[]> libMap, PlatformTarget basePlatform) { + String[] abis, Map<String, String[]> libMap, PlatformTarget basePlatform) { if (location.endsWith(File.separator) == false) { location = location + File.separator; } @@ -99,6 +103,14 @@ final class AddOnTarget implements IAndroidTarget { mDescription = description; mBasePlatform = basePlatform; + //set compatibility mode + if (abis.length > 0) { + mAbis = abis; + } else { + mAbiCompatibilityMode = true; + mAbis = new String[] { SdkConstants.ABI_ARMEABI }; + } + // handle the optional libraries. if (libMap != null) { mLibraries = new IOptionalLibrary[libMap.size()]; @@ -121,6 +133,25 @@ final class AddOnTarget implements IAndroidTarget { return mName; } + /** + * Return the full path for images + * @param abiType type of the abi + * @return complete path where the image files are located + */ + public String getImagePath(String abiType) { + + if (mAbiCompatibilityMode) { + // Use legacy directory structure if only arm + return mLocation + SdkConstants.OS_IMAGES_FOLDER; + } else { + return mLocation + SdkConstants.OS_IMAGES_FOLDER + abiType + File.separator; + } + } + + public String[] getAbiList() { + return mAbis; + } + public String getVendor() { return mVendor; } @@ -160,8 +191,6 @@ final class AddOnTarget implements IAndroidTarget { public String getPath(int pathId) { switch (pathId) { - case IMAGES: - return mLocation + SdkConstants.OS_IMAGES_FOLDER; case SKINS: return mLocation + SdkConstants.OS_SKINS_FOLDER; case DOCS: diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java index c0dcaa7..2ca7763 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/IAndroidTarget.java @@ -29,8 +29,6 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { public final static int ANDROID_JAR = 1; /** OS Path to the "framework.aidl" file. */ public final static int ANDROID_AIDL = 2; - /** OS Path to "images" folder which contains the emulator system images. */ - public final static int IMAGES = 3; /** OS Path to the "samples" folder which contains sample projects. */ public final static int SAMPLES = 4; /** OS Path to the "skins" folder which contains the emulator skins. */ @@ -227,6 +225,16 @@ public interface IAndroidTarget extends Comparable<IAndroidTarget> { int getUsbVendorId(); /** + * Returns array of permitted processor architectures + */ + public String[] getAbiList(); + + /** + * Returns string to append to images directory for current ProcessorType + */ + public String getImagePath(String abiType); + + /** * Returns whether the given target is compatible with the receiver. * <p/> * This means that a project using the receiver's target can run on the given target. diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java index 6aeeade..62720e7 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/PlatformTarget.java @@ -21,6 +21,7 @@ import com.android.sdklib.util.SparseArray; import java.io.File; import java.util.Collections; import java.util.Map; +import java.util.ArrayList; /** * Represents a platform target in the SDK. @@ -43,21 +44,25 @@ final class PlatformTarget implements IAndroidTarget { private final Map<String, String> mProperties; private final SparseArray<String> mPaths = new SparseArray<String>(); private String[] mSkins; + private String[] mAbis; + private boolean mAbiCompatibilityMode; /** * Creates a Platform target. * @param sdkOsPath the root folder of the SDK * @param platformOSPath the root folder of the platform component - * @param properties the platform properties * @param apiLevel the API Level * @param codeName the codename. can be null. * @param versionName the version name of the platform. * @param revision the revision of the platform component. + * @param abis the list of supported abis + * @param properties the platform properties */ @SuppressWarnings("deprecation") - PlatformTarget(String sdkOsPath, String platformOSPath, Map<String, String> properties, - int apiLevel, String codeName, String versionName, int revision) { + PlatformTarget(String sdkOsPath, String platformOSPath, int apiLevel, + String codeName, String versionName, int revision, String[] abis, + Map<String, String> properties) { if (platformOSPath.endsWith(File.separator) == false) { platformOSPath = platformOSPath + File.separator; } @@ -79,7 +84,6 @@ final class PlatformTarget implements IAndroidTarget { mPaths.put(ANDROID_AIDL, mRootFolderOsPath + SdkConstants.FN_FRAMEWORK_AIDL); mPaths.put(ANDROID_RS, mRootFolderOsPath + SdkConstants.OS_FRAMEWORK_RS); mPaths.put(ANDROID_RS_CLANG, mRootFolderOsPath + SdkConstants.OS_FRAMEWORK_RS_CLANG); - mPaths.put(IMAGES, mRootFolderOsPath + SdkConstants.OS_IMAGES_FOLDER); mPaths.put(SAMPLES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_SAMPLES_FOLDER); mPaths.put(SKINS, mRootFolderOsPath + SdkConstants.OS_SKINS_FOLDER); mPaths.put(TEMPLATES, mRootFolderOsPath + SdkConstants.OS_PLATFORM_TEMPLATES_FOLDER); @@ -112,6 +116,36 @@ final class PlatformTarget implements IAndroidTarget { SdkConstants.FN_DX); mPaths.put(DX_JAR, sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR); + + //set compatibility mode, abis length would be 0 for older APIs + if (abis.length > 0) { + mAbis = abis; + } else { + mAbiCompatibilityMode = true; + mAbis = new String[] { SdkConstants.ABI_ARMEABI }; + } + + } + + /** + * Return the full path for images + * @param abiType type of the abi + * @return complete path where the image files are located + */ + public String getImagePath(String abiType) { + if (mAbiCompatibilityMode) { + // Use legacy directory structure if only arm is supported + return mRootFolderOsPath + SdkConstants.OS_IMAGES_FOLDER; + } else { + return mRootFolderOsPath + SdkConstants.OS_IMAGES_FOLDER + abiType + File.separator; + } + } + + /** + * Retrieve and return the list of abis + */ + public String[] getAbiList() { + return mAbis; } public String getLocation() { diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java index 37265b1..f75d3a9 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java @@ -16,6 +16,8 @@ package com.android.sdklib; +import com.android.AndroidConstants; + import java.io.File; /** @@ -130,9 +132,13 @@ public final class SdkConstants { public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$ - /** emulator executable (with extension for the current OS) */ - public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? - "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$ + /** emulator executable (_WITHOUT_ extension for the current OS) */ + public final static String FN_EMULATOR = + "emulator"; //$NON-NLS-1$ //$NON-NLS-2$ + + /** emulator executable extension for the current OS */ + public final static String FN_EMULATOR_EXTENSION = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + ".exe" : ""; //$NON-NLS-1$ //$NON-NLS-2$ /** zipalign executable (with extension for the current OS) */ public final static String FN_ZIPALIGN = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? @@ -180,28 +186,6 @@ public final class SdkConstants { public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$ /** Default output folder name, i.e. "bin" */ public final static String FD_OUTPUT = "bin"; //$NON-NLS-1$ - /** Default anim resource folder name, i.e. "anim" */ - public final static String FD_ANIM = "anim"; //$NON-NLS-1$ - /** Default animator resource folder name, i.e. "animator" */ - public final static String FD_ANIMATOR = "animator"; //$NON-NLS-1$ - /** Default color resource folder name, i.e. "color" */ - public final static String FD_COLOR = "color"; //$NON-NLS-1$ - /** Default drawable resource folder name, i.e. "drawable" */ - public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$ - /** Default interpolator resource folder name, i.e. "interpolator" */ - public final static String FD_INTERPOLATOR = "interpolator"; //$NON-NLS-1$ - /** Default layout resource folder name, i.e. "layout" */ - public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$ - /** Default menu resource folder name, i.e. "menu" */ - public final static String FD_MENU = "menu"; //$NON-NLS-1$ - /** Default menu resource folder name, i.e. "mipmap" */ - public final static String FD_MIPMAP = "mipmap"; //$NON-NLS-1$ - /** Default values resource folder name, i.e. "values" */ - public final static String FD_VALUES = "values"; //$NON-NLS-1$ - /** Default xml resource folder name, i.e. "xml" */ - public final static String FD_XML = "xml"; //$NON-NLS-1$ - /** Default raw resource folder name, i.e. "raw" */ - public final static String FD_RAW = "raw"; //$NON-NLS-1$ /** proguard output folder for mapping, etc.. files */ public final static String FD_PROGUARD = "proguard"; //$NON-NLS-1$ @@ -223,6 +207,10 @@ public final class SdkConstants { public static final String FD_DOCS_REFERENCE = "reference"; /** Name of the SDK images folder. */ public final static String FD_IMAGES = "images"; + /** Name of the processors to support. */ + public final static String ABI_ARMEABI = "armeabi"; + public final static String ABI_INTEL_ATOM = "x86"; + /** Name of the SDK skins folder. */ public final static String FD_SKINS = "skins"; /** Name of the SDK samples folder. */ @@ -336,11 +324,13 @@ public final class SdkConstants { /** Path of the attrs.xml file relative to a platform folder. */ public final static String OS_PLATFORM_ATTRS_XML = - OS_PLATFORM_RESOURCES_FOLDER + FD_VALUES + File.separator + FN_ATTRS_XML; + OS_PLATFORM_RESOURCES_FOLDER + AndroidConstants.FD_RES_VALUES + File.separator + + FN_ATTRS_XML; /** Path of the attrs_manifest.xml file relative to a platform folder. */ public final static String OS_PLATFORM_ATTRS_MANIFEST_XML = - OS_PLATFORM_RESOURCES_FOLDER + FD_VALUES + File.separator + FN_ATTRS_MANIFEST_XML; + OS_PLATFORM_RESOURCES_FOLDER + AndroidConstants.FD_RES_VALUES + File.separator + + FN_ATTRS_MANIFEST_XML; /** Path of the layoutlib.jar file relative to a platform folder. */ public final static String OS_PLATFORM_LAYOUTLIB_JAR = diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java index 3e6e23e..d924d3d 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java @@ -18,11 +18,11 @@ package com.android.sdklib; import com.android.annotations.VisibleForTesting; import com.android.annotations.VisibleForTesting.Visibility; +import com.android.io.FileWrapper; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.AndroidVersion.AndroidVersionException; import com.android.sdklib.internal.project.ProjectProperties; -import com.android.sdklib.io.FileWrapper; import com.android.util.Pair; import java.io.File; @@ -233,13 +233,17 @@ public class SdkManager { /** * Loads the Platforms from the SDK. + * Creates the "platforms" folder if necessary. + * * @param sdkOsPath Location of the SDK * @param list the list to fill with the platforms. * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. + * @throws RuntimeException when the "platforms" folder is missing and cannot be created. */ private static void loadPlatforms(String sdkOsPath, ArrayList<IAndroidTarget> list, ISdkLog log) { File platformFolder = new File(sdkOsPath, SdkConstants.FD_PLATFORMS); + if (platformFolder.isDirectory()) { File[] platforms = platformFolder.listFiles(); @@ -257,15 +261,18 @@ public class SdkManager { return; } - String message = null; - if (platformFolder.exists() == false) { - message = "%s is missing."; + // Try to create it or complain if something else is in the way. + if (!platformFolder.exists()) { + if (!platformFolder.mkdir()) { + throw new RuntimeException( + String.format("Failed to create %1$s.", + platformFolder.getAbsolutePath())); + } } else { - message = "%s is not a folder."; + throw new RuntimeException( + String.format("%1$s is not a folder.", + platformFolder.getAbsolutePath())); } - - throw new IllegalArgumentException(String.format(message, - platformFolder.getAbsolutePath())); } /** @@ -355,15 +362,17 @@ public class SdkManager { return null; } + String[] abiList = getAbiList(platformFolder.getAbsolutePath()); // create the target. PlatformTarget target = new PlatformTarget( sdkOsPath, platformFolder.getAbsolutePath(), - map, apiNumber, apiCodename, apiName, - revision); + revision, + abiList, + map); // need to parse the skins. String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS)); @@ -380,15 +389,43 @@ public class SdkManager { return null; } + /** + * Get all the abi types supported for a given target + * @param path Path where the images folder for a target is located + * @return an array of strings containing all the abi names for the target + */ + private static String[] getAbiList(String path) { + ArrayList<String> list = new ArrayList<String>(); + + File imagesFolder = new File(path + File.separator + SdkConstants.OS_IMAGES_FOLDER); + File[] files = imagesFolder.listFiles(); + + if (files != null) { + // Loop through Images directory. If subdirectories exist, set multiprocessor mode + for (File file : files) { + if (file.isDirectory()) { + list.add(file.getName()); + } + } + } + String[] abis = new String[list.size()]; + list.toArray(abis); + + return abis; + } /** * Loads the Add-on from the SDK. + * Creates the "add-ons" folder if necessary. + * * @param osSdkPath Location of the SDK * @param list the list to fill with the add-ons. * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. + * @throws RuntimeException when the "add-ons" folder is missing and cannot be created. */ private static void loadAddOns(String osSdkPath, ArrayList<IAndroidTarget> list, ISdkLog log) { File addonFolder = new File(osSdkPath, SdkConstants.FD_ADDONS); + if (addonFolder.isDirectory()) { File[] addons = addonFolder.listFiles(); @@ -407,15 +444,18 @@ public class SdkManager { return; } - String message = null; - if (addonFolder.exists() == false) { - message = "%s is missing."; + // Try to create it or complain if something else is in the way. + if (!addonFolder.exists()) { + if (!addonFolder.mkdir()) { + throw new RuntimeException( + String.format("Failed to create %1$s.", + addonFolder.getAbsolutePath())); + } } else { - message = "%s is not a folder."; + throw new RuntimeException( + String.format("%1$s is not a folder.", + addonFolder.getAbsolutePath())); } - - throw new IllegalArgumentException(String.format(message, - addonFolder.getAbsolutePath())); } /** @@ -514,8 +554,9 @@ public class SdkManager { } } + String[] abiList = getAbiList(addonDir.getAbsolutePath()); AddOnTarget target = new AddOnTarget(addonDir.getAbsolutePath(), name, vendor, - revisionValue, description, libMap, baseTarget); + revisionValue, description, abiList, libMap, baseTarget); // need to parse the skins. String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS)); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java new file mode 100755 index 0000000..81ffa5d --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdInfo.java @@ -0,0 +1,334 @@ +/* + * 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.sdklib.internal.avd; + +import com.android.prefs.AndroidLocation.AndroidLocationException; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; + +import java.io.File; +import java.util.Collections; +import java.util.Map; + +/** + * An immutable structure describing an Android Virtual Device. + */ +public final class AvdInfo implements Comparable<AvdInfo> { + + /** + * Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid. + */ + public static enum AvdStatus { + /** No error */ + OK, + /** Missing 'path' property in the ini file */ + ERROR_PATH, + /** Missing config.ini file in the AVD data folder */ + ERROR_CONFIG, + /** Missing 'target' property in the ini file */ + ERROR_TARGET_HASH, + /** Target was not resolved from its hash */ + ERROR_TARGET, + /** Unable to parse config.ini */ + ERROR_PROPERTIES, + /** System Image folder in config.ini doesn't exist */ + ERROR_IMAGE_DIR; + } + + private final String mName; + private final File mIniFile; + private final String mFolderPath; + private final String mTargetHash; + private final IAndroidTarget mTarget; + private final String mAbiType; + private final Map<String, String> mProperties; + private final AvdStatus mStatus; + + /** + * Creates a new valid AVD info. Values are immutable. + * <p/> + * Such an AVD is available and can be used. + * The error string is set to null. + * + * @param name The name of the AVD (for display or reference) + * @param iniFile The path to the config.ini file + * @param folderPath The path to the data directory + * @param targetHash the target hash + * @param target The target. Can be null, if the target was not resolved. + * @param abiType Name of the abi. + * @param properties The property map. Cannot be null. + */ + public AvdInfo(String name, + File iniFile, + String folderPath, + String targetHash, + IAndroidTarget target, + String abiType, + Map<String, String> properties) { + this(name, iniFile, folderPath, targetHash, target, abiType, properties, AvdStatus.OK); + } + + /** + * Creates a new <em>invalid</em> AVD info. Values are immutable. + * <p/> + * Such an AVD is not complete and cannot be used. + * The error string must be non-null. + * + * @param name The name of the AVD (for display or reference) + * @param iniFile The path to the config.ini file + * @param folderPath The path to the data directory + * @param targetHash the target hash + * @param target The target. Can be null, if the target was not resolved. + * @param abiType Name of the abi. + * @param properties The property map. Can be null. + * @param status The {@link AvdStatus} of this AVD. Cannot be null. + */ + public AvdInfo(String name, + File iniFile, + String folderPath, + String targetHash, + IAndroidTarget target, + String abiType, + Map<String, String> properties, + AvdStatus status) { + mName = name; + mIniFile = iniFile; + mFolderPath = folderPath; + mTargetHash = targetHash; + mTarget = target; + mAbiType = abiType; + mProperties = properties == null ? null : Collections.unmodifiableMap(properties); + mStatus = status; + } + + /** Returns the name of the AVD. */ + public String getName() { + return mName; + } + + /** Returns the path of the AVD data directory. */ + public String getDataFolderPath() { + return mFolderPath; + } + + /** Returns the processor type of the AVD. */ + public String getAbiType() { + return mAbiType; + } + + /** Convenience function to return a more user friendly name of the abi type. */ + public static String getPrettyAbiType(String raw) { + String s = null; + if (raw.equalsIgnoreCase(SdkConstants.ABI_ARMEABI)) { + s = "ARM (" + SdkConstants.ABI_ARMEABI + ")"; + } + else if (raw.equalsIgnoreCase(SdkConstants.ABI_INTEL_ATOM)) { + s = "Intel Atom (" + SdkConstants.ABI_INTEL_ATOM + ")"; + } + else { + s = raw + " (" + raw + ")"; + } + return s; + } + + /** + * Returns the emulator executable path + * @param sdkPath path of the sdk + * @return path of the emulator executable + */ + public String getEmulatorPath(String sdkPath) { + String path = sdkPath + SdkConstants.OS_SDK_TOOLS_FOLDER; + + // Start with base name of the emulator + path = path + SdkConstants.FN_EMULATOR; + + // If not using ARM, add processor type to emulator command line + boolean useAbi = !getAbiType().equalsIgnoreCase(SdkConstants.ABI_ARMEABI); + + if (useAbi) { + path = path + "-" + getAbiType(); //$NON-NLS-1$ + } + // Add OS appropriate emulator extension (e.g., .exe on windows) + path = path + SdkConstants.FN_EMULATOR_EXTENSION; + + // HACK: The AVD manager should look for "emulator" or for "emulator-abi" (if not arm). + // However this is a transition period and we don't have that unified "emulator" binary + // in AOSP so if we can't find the generic one, look for an abi-specific one with the + // special case that the armeabi one is actually named emulator-arm. + // TODO remove this kludge once no longer necessary. + if (!useAbi && !(new File(path).isFile())) { + path = sdkPath + SdkConstants.OS_SDK_TOOLS_FOLDER; + path = path + SdkConstants.FN_EMULATOR; + path = path + "-" //$NON-NLS-1$ + + getAbiType().replace(SdkConstants.ABI_ARMEABI, "arm"); //$NON-NLS-1$ + path = path + SdkConstants.FN_EMULATOR_EXTENSION; + } + + return path; + } + + /** + * Returns the target hash string. + */ + public String getTargetHash() { + return mTargetHash; + } + + /** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */ + public IAndroidTarget getTarget() { + return mTarget; + } + + /** Returns the {@link AvdStatus} of the receiver. */ + public AvdStatus getStatus() { + return mStatus; + } + + /** + * Helper method that returns the default AVD folder that would be used for a given + * AVD name <em>if and only if</em> the AVD was created with the default choice. + * <p/> + * Callers must NOT use this to "guess" the actual folder from an actual AVD since + * the purpose of the AVD .ini file is to be able to change this folder. Callers + * should however use this to create a new {@link AvdInfo} to setup its data folder + * to the default. + * <p/> + * The default is {@code getDefaultAvdFolder()/avdname.avd/}. + * <p/> + * For an actual existing AVD, callers must use {@link #getDataFolderPath()} instead. + * + * @param manager The AVD Manager, used to get the AVD storage path. + * @param avdName The name of the desired AVD. + * @throws AndroidLocationException if there's a problem getting android root directory. + */ + public static File getDefaultAvdFolder(AvdManager manager, String avdName) + throws AndroidLocationException { + return new File(manager.getBaseAvdFolder(), + avdName + AvdManager.AVD_FOLDER_EXTENSION); + } + + /** + * Helper method that returns the .ini {@link File} for a given AVD name. + * <p/> + * The default is {@code getDefaultAvdFolder()/avdname.ini}. + * + * @param manager The AVD Manager, used to get the AVD storage path. + * @param avdName The name of the desired AVD. + * @throws AndroidLocationException if there's a problem getting android root directory. + */ + public static File getDefaultIniFile(AvdManager manager, String avdName) + throws AndroidLocationException { + String avdRoot = manager.getBaseAvdFolder(); + return new File(avdRoot, avdName + AvdManager.INI_EXTENSION); + } + + /** + * Returns the .ini {@link File} for this AVD. + */ + public File getIniFile() { + return mIniFile; + } + + /** + * Helper method that returns the Config {@link File} for a given AVD name. + */ + public static File getConfigFile(String path) { + return new File(path, AvdManager.CONFIG_INI); + } + + /** + * Returns the Config {@link File} for this AVD. + */ + public File getConfigFile() { + return getConfigFile(mFolderPath); + } + + /** + * Returns an unmodifiable map of properties for the AVD. This can be null. + */ + public Map<String, String> getProperties() { + return mProperties; + } + + /** + * Returns the error message for the AVD or <code>null</code> if {@link #getStatus()} + * returns {@link AvdStatus#OK} + */ + public String getErrorMessage() { + switch (mStatus) { + case ERROR_PATH: + return String.format("Missing AVD 'path' property in %1$s", getIniFile()); + case ERROR_CONFIG: + return String.format("Missing config.ini file in %1$s", mFolderPath); + case ERROR_TARGET_HASH: + return String.format("Missing 'target' property in %1$s", getIniFile()); + case ERROR_TARGET: + return String.format("Unknown target '%1$s' in %2$s", + mTargetHash, getIniFile()); + case ERROR_PROPERTIES: + return String.format("Failed to parse properties from %1$s", + getConfigFile()); + case ERROR_IMAGE_DIR: + return String.format( + "Invalid value in image.sysdir. Run 'android update avd -n %1$s'", + mName); + case OK: + assert false; + return null; + } + + return null; + } + + /** + * Returns whether an emulator is currently running the AVD. + */ + public boolean isRunning() { + File f = new File(mFolderPath, "userdata-qemu.img.lock"); //$NON-NLS-1$ + return f.isFile(); + } + + /** + * Compares this object with the specified object for order. Returns a + * negative integer, zero, or a positive integer as this object is less + * than, equal to, or greater than the specified object. + * + * @param o the Object to be compared. + * @return a negative integer, zero, or a positive integer as this object is + * less than, equal to, or greater than the specified object. + */ + public int compareTo(AvdInfo o) { + // first handle possible missing targets (if the AVD failed to load for unresolved targets) + if (mTarget == null && o != null && o.mTarget == null) { + return 0; + } if (mTarget == null) { + return +1; + } else if (o == null || o.mTarget == null) { + return -1; + } + + // then compare the targets + int targetDiff = mTarget.compareTo(o.mTarget); + + if (targetDiff == 0) { + // same target? compare on the avd name + return mName.compareTo(o.mName); + } + + return targetDiff; + } +} diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java index eba8e07..c9a3561 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/avd/AvdManager.java @@ -16,15 +16,16 @@ package com.android.sdklib.internal.avd; +import com.android.io.FileWrapper; import com.android.prefs.AndroidLocation; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus; +import com.android.sdklib.internal.avd.AvdInfo.AvdStatus; import com.android.sdklib.internal.project.ProjectProperties; -import com.android.sdklib.io.FileWrapper; +import com.android.util.Pair; import java.io.BufferedReader; import java.io.File; @@ -37,7 +38,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -47,7 +47,7 @@ import java.util.regex.Pattern; /** * Android Virtual Device Manager to manage AVDs. */ -public final class AvdManager { +public class AvdManager { /** * Exception thrown when something is wrong with a target path. @@ -66,6 +66,13 @@ public final class AvdManager { public final static String AVD_INFO_TARGET = "target"; //$NON-NLS-1$ /** + * AVD/config.ini key name representing the abi type of the specific avd + * + */ + public final static String AVD_INI_ABI_TYPE = "abi.type"; //$NON-NLS-1$ + + + /** * AVD/config.ini key name representing the SDK-relative path of the skin folder, if any, * or a 320x480 like constant for a numeric skin size. * @@ -92,6 +99,7 @@ public final class AvdManager { * This property is for UI purposes only. It is not used by the emulator. * * @see #SDCARD_SIZE_PATTERN + * @see #parseSdcardSize(String, String[]) */ public final static String AVD_INI_SDCARD_SIZE = "sdcard.size"; //$NON-NLS-1$ /** @@ -123,11 +131,11 @@ public final class AvdManager { public final static Pattern NUMERIC_SKIN_SIZE = Pattern.compile("([0-9]{2,})x([0-9]{2,})"); //$NON-NLS-1$ private final static String USERDATA_IMG = "userdata.img"; //$NON-NLS-1$ - private final static String CONFIG_INI = "config.ini"; //$NON-NLS-1$ + final static String CONFIG_INI = "config.ini"; //$NON-NLS-1$ private final static String SDCARD_IMG = "sdcard.img"; //$NON-NLS-1$ private final static String SNAPSHOTS_IMG = "snapshots.img"; //$NON-NLS-1$ - private final static String INI_EXTENSION = ".ini"; //$NON-NLS-1$ + final static String INI_EXTENSION = ".ini"; //$NON-NLS-1$ private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + //$NON-NLS-1$ INI_EXTENSION + "$", //$NON-NLS-1$ Pattern.CASE_INSENSITIVE); @@ -137,8 +145,26 @@ public final class AvdManager { /** * Pattern for matching SD Card sizes, e.g. "4K" or "16M". + * Callers should use {@link #parseSdcardSize(String, String[])} instead of using this directly. + */ + private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("(\\d+)([KMG])"); //$NON-NLS-1$ + + /** + * Minimal size of an SDCard image file in bytes. Currently 9 MiB. + */ + + public static final long SDCARD_MIN_BYTE_SIZE = 9<<20; + /** + * Maximal size of an SDCard image file in bytes. Currently 1023 GiB. */ - public final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("(\\d+)([MK])"); //$NON-NLS-1$ + public static final long SDCARD_MAX_BYTE_SIZE = 1023L<<30; + + /** The sdcard string represents a valid number but the size is outside of the allowed range. */ + public final static int SDCARD_SIZE_NOT_IN_RANGE = 0; + /** The sdcard string looks like a size number+suffix but the number failed to decode. */ + public final static int SDCARD_SIZE_INVALID = -1; + /** The sdcard string doesn't look like a size, it might be a path instead. */ + public final static int SDCARD_NOT_SIZE_PATTERN = -2; /** Regex used to validate characters that compose an AVD name. */ public final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+"); //$NON-NLS-1$ @@ -148,212 +174,21 @@ public final class AvdManager { public final static String HARDWARE_INI = "hardware.ini"; //$NON-NLS-1$ - /** An immutable structure describing an Android Virtual Device. */ - public static final class AvdInfo implements Comparable<AvdInfo> { - - /** - * Status for an {@link AvdInfo}. Indicates whether or not this AVD is valid. - */ - public static enum AvdStatus { - /** No error */ - OK, - /** Missing 'path' property in the ini file */ - ERROR_PATH, - /** Missing config.ini file in the AVD data folder */ - ERROR_CONFIG, - /** Missing 'target' property in the ini file */ - ERROR_TARGET_HASH, - /** Target was not resolved from its hash */ - ERROR_TARGET, - /** Unable to parse config.ini */ - ERROR_PROPERTIES, - /** System Image folder in config.ini doesn't exist */ - ERROR_IMAGE_DIR; - } - - private final String mName; - private final String mPath; - private final String mTargetHash; - private final IAndroidTarget mTarget; - private final Map<String, String> mProperties; - private final AvdStatus mStatus; - - /** - * Creates a new valid AVD info. Values are immutable. - * <p/> - * Such an AVD is available and can be used. - * The error string is set to null. - * - * @param name The name of the AVD (for display or reference) - * @param path The path to the config.ini file - * @param targetHash the target hash - * @param target The target. Can be null, if the target was not resolved. - * @param properties The property map. Cannot be null. - */ - public AvdInfo(String name, String path, String targetHash, IAndroidTarget target, - Map<String, String> properties) { - this(name, path, targetHash, target, properties, AvdStatus.OK); - } - - /** - * Creates a new <em>invalid</em> AVD info. Values are immutable. - * <p/> - * Such an AVD is not complete and cannot be used. - * The error string must be non-null. - * - * @param name The name of the AVD (for display or reference) - * @param path The path to the config.ini file - * @param targetHash the target hash - * @param target The target. Can be null, if the target was not resolved. - * @param properties The property map. Can be null. - * @param status The {@link AvdStatus} of this AVD. Cannot be null. - */ - public AvdInfo(String name, String path, String targetHash, IAndroidTarget target, - Map<String, String> properties, AvdStatus status) { - mName = name; - mPath = path; - mTargetHash = targetHash; - mTarget = target; - mProperties = properties == null ? null : Collections.unmodifiableMap(properties); - mStatus = status; - } - - /** Returns the name of the AVD. */ - public String getName() { - return mName; - } - - /** Returns the path of the AVD data directory. */ - public String getPath() { - return mPath; - } - - /** - * Returns the target hash string. - */ - public String getTargetHash() { - return mTargetHash; - } - - /** Returns the target of the AVD, or <code>null</code> if it has not been resolved. */ - public IAndroidTarget getTarget() { - return mTarget; - } - - /** Returns the {@link AvdStatus} of the receiver. */ - public AvdStatus getStatus() { - return mStatus; - } - - /** - * Helper method that returns the .ini {@link File} for a given AVD name. - * @throws AndroidLocationException if there's a problem getting android root directory. - */ - public static File getIniFile(String name) throws AndroidLocationException { - String avdRoot; - avdRoot = getBaseAvdFolder(); - return new File(avdRoot, name + INI_EXTENSION); - } - - /** - * Returns the .ini {@link File} for this AVD. - * @throws AndroidLocationException if there's a problem getting android root directory. - */ - public File getIniFile() throws AndroidLocationException { - return getIniFile(mName); - } - - /** - * Helper method that returns the Config {@link File} for a given AVD name. - */ - public static File getConfigFile(String path) { - return new File(path, CONFIG_INI); - } - - /** - * Returns the Config {@link File} for this AVD. - */ - public File getConfigFile() { - return getConfigFile(mPath); - } - - /** - * Returns an unmodifiable map of properties for the AVD. This can be null. - */ - public Map<String, String> getProperties() { - return mProperties; - } - - /** - * Returns the error message for the AVD or <code>null</code> if {@link #getStatus()} - * returns {@link AvdStatus#OK} - */ - public String getErrorMessage() { - try { - switch (mStatus) { - case ERROR_PATH: - return String.format("Missing AVD 'path' property in %1$s", getIniFile()); - case ERROR_CONFIG: - return String.format("Missing config.ini file in %1$s", mPath); - case ERROR_TARGET_HASH: - return String.format("Missing 'target' property in %1$s", getIniFile()); - case ERROR_TARGET: - return String.format("Unknown target '%1$s' in %2$s", - mTargetHash, getIniFile()); - case ERROR_PROPERTIES: - return String.format("Failed to parse properties from %1$s", - getConfigFile()); - case ERROR_IMAGE_DIR: - return String.format( - "Invalid value in image.sysdir. Run 'android update avd -n %1$s'", - mName); - case OK: - assert false; - return null; - } - } catch (AndroidLocationException e) { - return "Unable to get HOME folder."; - } - - return null; - } - - /** - * Returns whether an emulator is currently running the AVD. - */ - public boolean isRunning() { - File f = new File(mPath, "userdata-qemu.img.lock"); - return f.isFile(); - } - + /** + * Status returned by {@link AvdManager#isAvdNameConflicting(String)}. + */ + public static enum AvdConflict { + /** There is no known conflict for the given AVD name. */ + NO_CONFLICT, + /** The AVD name conflicts with an existing valid AVD. */ + CONFLICT_EXISTING_AVD, + /** The AVD name conflicts with an existing invalid AVD. */ + CONFLICT_INVALID_AVD, /** - * Compares this object with the specified object for order. Returns a - * negative integer, zero, or a positive integer as this object is less - * than, equal to, or greater than the specified object. - * - * @param o the Object to be compared. - * @return a negative integer, zero, or a positive integer as this object is - * less than, equal to, or greater than the specified object. + * The AVD name does not conflict with any known AVD however there are + * files or directory that would cause a conflict if this were to be created. */ - public int compareTo(AvdInfo o) { - // first handle possible missing targets (if the AVD failed to load for - // unresolved targets. - if (mTarget == null) { - return +1; - } else if (o.mTarget == null) { - return -1; - } - - // then compare the targets - int targetDiff = mTarget.compareTo(o.mTarget); - - if (targetDiff == 0) { - // same target? compare on the avd name - return mName.compareTo(o.mName); - } - - return targetDiff; - } + CONFLICT_EXISTING_PATH, } private final ArrayList<AvdInfo> mAllAvdList = new ArrayList<AvdInfo>(); @@ -362,15 +197,6 @@ public final class AvdManager { private final SdkManager mSdkManager; /** - * Returns the base folder where AVDs are created. - * - * @throws AndroidLocationException - */ - public static String getBaseAvdFolder() throws AndroidLocationException { - return AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; - } - - /** * Creates an AVD Manager for a given SDK represented by a {@link SdkManager}. * @param sdkManager The SDK. * @param log The log object to receive the log of the initial loading of the AVDs. @@ -386,6 +212,16 @@ public final class AvdManager { } /** + * Returns the base folder where AVDs are created. + * + * @throws AndroidLocationException + */ + public String getBaseAvdFolder() throws AndroidLocationException { + assert AndroidLocation.getFolder().endsWith(File.separator); + return AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; + } + + /** * Returns the {@link SdkManager} associated with the {@link AvdManager}. */ public SdkManager getSdkManager() { @@ -393,6 +229,71 @@ public final class AvdManager { } /** + * Parse the sdcard string to decode the size. + * Returns: + * <ul> + * <li> The size in bytes > 0 if the sdcard string is a valid size in the allowed range. + * <li> {@link #SDCARD_SIZE_NOT_IN_RANGE} (0) + * if the sdcard string is a valid size NOT in the allowed range. + * <li> {@link #SDCARD_SIZE_INVALID} (-1) + * if the sdcard string is number that fails to parse correctly. + * <li> {@link #SDCARD_NOT_SIZE_PATTERN} (-2) + * if the sdcard string is not a number, in which case it's probably a file path. + * </ul> + * + * @param sdcard The sdcard string, which can be a file path, a size string or something else. + * @param parsedStrings If non-null, an array of 2 strings. The first string will be + * filled with the parsed numeric size and the second one will be filled with the + * parsed suffix. This is filled even if the returned size is deemed out of range or + * failed to parse. The values are null if the sdcard is not a size pattern. + * @return A size in byte if > 0, or {@link #SDCARD_SIZE_NOT_IN_RANGE}, + * {@link #SDCARD_SIZE_INVALID} or {@link #SDCARD_NOT_SIZE_PATTERN} as error codes. + */ + public static long parseSdcardSize(String sdcard, String[] parsedStrings) { + + if (parsedStrings != null) { + assert parsedStrings.length == 2; + parsedStrings[0] = null; + parsedStrings[1] = null; + } + + Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard); + if (m.matches()) { + if (parsedStrings != null) { + assert parsedStrings.length == 2; + parsedStrings[0] = m.group(1); + parsedStrings[1] = m.group(2); + } + + // get the sdcard values for checks + try { + long sdcardSize = Long.parseLong(m.group(1)); + + String sdcardSizeModifier = m.group(2); + if ("K".equals(sdcardSizeModifier)) { //$NON-NLS-1$ + sdcardSize <<= 10; + } else if ("M".equals(sdcardSizeModifier)) { //$NON-NLS-1$ + sdcardSize <<= 20; + } else if ("G".equals(sdcardSizeModifier)) { //$NON-NLS-1$ + sdcardSize <<= 30; + } + + if (sdcardSize < SDCARD_MIN_BYTE_SIZE || + sdcardSize > SDCARD_MAX_BYTE_SIZE) { + return SDCARD_SIZE_NOT_IN_RANGE; + } + + return sdcardSize; + } catch (NumberFormatException e) { + // This could happen if the number is too large to fit in a long. + return SDCARD_SIZE_INVALID; + } + } + + return SDCARD_NOT_SIZE_PATTERN; + } + + /** * Returns all the existing AVDs. * @return a newly allocated array containing all the AVDs. */ @@ -476,6 +377,53 @@ public final class AvdManager { } /** + * Returns whether this AVD name would generate a conflict. + * + * @param name the name of the AVD to return + * @return A pair of {@link AvdConflict} and the path or AVD name that conflicts. + */ + public Pair<AvdConflict, String> isAvdNameConflicting(String name) { + + boolean ignoreCase = SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS; + + // Check whether we have a conflict with an existing or invalid AVD + // known to the manager. + synchronized (mAllAvdList) { + for (AvdInfo info : mAllAvdList) { + String name2 = info.getName(); + if (name2.equals(name) || (ignoreCase && name2.equalsIgnoreCase(name))) { + if (info.getStatus() == AvdStatus.OK) { + return Pair.of(AvdConflict.CONFLICT_EXISTING_AVD, name2); + } else { + return Pair.of(AvdConflict.CONFLICT_INVALID_AVD, name2); + } + } + } + } + + // No conflict with known AVDs. + // Are some existing files/folders in the way of creating this AVD? + + try { + File file = AvdInfo.getDefaultIniFile(this, name); + if (file.exists()) { + return Pair.of(AvdConflict.CONFLICT_EXISTING_PATH, file.getPath()); + } + + file = AvdInfo.getDefaultAvdFolder(this, name); + if (file.exists()) { + return Pair.of(AvdConflict.CONFLICT_EXISTING_PATH, file.getPath()); + } + + } catch (AndroidLocationException e) { + // ignore + } + + + return Pair.of(AvdConflict.NO_CONFLICT, null); + } + + /** * Reloads the AVD list. * @param log the log object to receive action logs. Cannot be null. * @throws AndroidLocationException if there was an error finding the location of the @@ -495,37 +443,38 @@ public final class AvdManager { } /** - * Creates a new AVD, but with no snapshot. - * - * See {@link #createAvd(File, String, IAndroidTarget, String, String, Map, boolean, boolean, ISdkLog)} - **/ - @Deprecated - public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target, String skinName, - String sdcard, Map<String, String> hardwareConfig, boolean removePrevious, - ISdkLog log) { - return createAvd(avdFolder, name, target, skinName, sdcard, hardwareConfig, removePrevious, - false, log); - } - - /** * Creates a new AVD. It is expected that there is no existing AVD with this name already. * * @param avdFolder the data folder for the AVD. It will be created as needed. - * @param name the name of the AVD + * Unless you want to locate it in a specific directory, the ideal default is + * {@code AvdManager.AvdInfo.getAvdFolder}. + * @param avdName the name of the AVD * @param target the target of the AVD + * @param abiType the abi type of the AVD * @param skinName the name of the skin. Can be null. Must have been verified by caller. * @param sdcard the parameter value for the sdCard. Can be null. This is either a path to * an existing sdcard image or a sdcard size (\d+, \d+K, \dM). * @param hardwareConfig the hardware setup for the AVD. Can be null to use defaults. - * @param removePrevious If true remove any previous files. * @param createSnapshot If true copy a blank snapshot image into the AVD. + * @param removePrevious If true remove any previous files. + * @param editExisting If true, edit an existing AVD, changing only the minimum required. + * This won't remove files unless required or unless {@code removePrevious} is set. * @param log the log object to receive action logs. Cannot be null. * @return The new {@link AvdInfo} in case of success (which has just been added to the * internal list) or null in case of failure. */ - public AvdInfo createAvd(File avdFolder, String name, IAndroidTarget target, - String skinName, String sdcard, Map<String,String> hardwareConfig, - boolean removePrevious, boolean createSnapshot, ISdkLog log) { + public AvdInfo createAvd( + File avdFolder, + String avdName, + IAndroidTarget target, + String abiType, + String skinName, + String sdcard, + Map<String,String> hardwareConfig, + boolean createSnapshot, + boolean removePrevious, + boolean editExisting, + ISdkLog log) { if (log == null) { throw new IllegalArgumentException("log cannot be null"); } @@ -542,8 +491,9 @@ public final class AvdManager { } catch (SecurityException e) { log.error(e, "Failed to delete %1$s", avdFolder.getAbsolutePath()); } - } else { - // AVD shouldn't already exist if removePrevious is false. + } else if (!editExisting) { + // AVD shouldn't already exist if removePrevious is false and + // we're not editing an existing AVD. log.error(null, "Folder %1$s is in the way. Use --force if you want to overwrite.", avdFolder.getAbsolutePath()); @@ -552,23 +502,28 @@ public final class AvdManager { } else { // create the AVD folder. avdFolder.mkdir(); + // We're not editing an existing AVD. + editExisting = false; } // actually write the ini file - iniFile = createAvdIniFile(name, avdFolder, target); + iniFile = createAvdIniFile(avdName, avdFolder, target, removePrevious); // writes the userdata.img in it. - String imagePath = target.getPath(IAndroidTarget.IMAGES); + String imagePath = target.getImagePath(abiType); + File userdataSrc = new File(imagePath, USERDATA_IMG); if (userdataSrc.exists() == false && target.isPlatform() == false) { - imagePath = target.getParent().getPath(IAndroidTarget.IMAGES); + imagePath = + target.getParent().getImagePath(abiType); userdataSrc = new File(imagePath, USERDATA_IMG); } if (userdataSrc.exists() == false) { - log.error(null, "Unable to find a '%1$s' file to copy into the AVD folder.", - USERDATA_IMG); + log.error(null, + "Unable to find a '%1$s' file of '%2$s' to copy into the AVD folder.", + USERDATA_IMG, imagePath); needCleanup = true; return null; } @@ -576,30 +531,48 @@ public final class AvdManager { copyImageFile(userdataSrc, userdataDest); + if (userdataDest.exists() == false) { + log.error(null, "Unable to create '%1$s' file in the AVD folder.", + userdataDest); + needCleanup = true; + return null; + } + // Config file. HashMap<String, String> values = new HashMap<String, String>(); - if (setImagePathProperties(target, values, log) == false) { - needCleanup = true; - return null; + if (setImagePathProperties(target, abiType, values, log) == false) { + log.error(null, "Failed to set image path properties in the AVD folder."); + needCleanup = true; + return null; } // Create the snapshot file if (createSnapshot) { - String toolsLib = mSdkManager.getLocation() + File.separator - + SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER; - File snapshotBlank = new File(toolsLib, SNAPSHOTS_IMG); - if (snapshotBlank.exists() == false) { - log.error(null, "Unable to find a '%2$s%1$s' file to copy into the AVD folder.", - SNAPSHOTS_IMG, toolsLib); - needCleanup = true; - return null; - } File snapshotDest = new File(avdFolder, SNAPSHOTS_IMG); - copyImageFile(snapshotBlank, snapshotDest); + if (snapshotDest.isFile() && editExisting) { + log.printf("Snapshot image already present, was not changed."); + + } else { + String toolsLib = mSdkManager.getLocation() + File.separator + + SdkConstants.OS_SDK_TOOLS_LIB_EMULATOR_FOLDER; + File snapshotBlank = new File(toolsLib, SNAPSHOTS_IMG); + if (snapshotBlank.exists() == false) { + log.error(null, + "Unable to find a '%2$s%1$s' file to copy into the AVD folder.", + SNAPSHOTS_IMG, toolsLib); + needCleanup = true; + return null; + } + + copyImageFile(snapshotBlank, snapshotDest); + } values.put(AVD_INI_SNAPSHOT_PRESENT, "true"); } + // Now the abi type + values.put(AVD_INI_ABI_TYPE, abiType); + // Now the skin. if (skinName == null || skinName.length() == 0) { skinName = target.getDefaultSkin(); @@ -616,6 +589,7 @@ public final class AvdManager { // assume skin name is valid String skinPath = getSkinRelativePath(skinName, target, log); if (skinPath == null) { + log.error(null, "Missing skinpath in the AVD folder."); needCleanup = true; return null; } @@ -625,54 +599,48 @@ public final class AvdManager { } if (sdcard != null && sdcard.length() > 0) { - File sdcardFile = new File(sdcard); - if (sdcardFile.isFile()) { - // sdcard value is an external sdcard, so we put its path into the config.ini - values.put(AVD_INI_SDCARD_PATH, sdcard); + // Sdcard is possibly a size. In that case we create a file called 'sdcard.img' + // in the AVD folder, and do not put any value in config.ini. + + long sdcardSize = parseSdcardSize(sdcard, null/*parsedStrings*/); + + if (sdcardSize == SDCARD_SIZE_NOT_IN_RANGE) { + log.error(null, "SD Card size must be in the range 9 MiB..1023 GiB."); + needCleanup = true; + return null; + + } else if (sdcardSize == SDCARD_SIZE_INVALID) { + log.error(null, "Unable to parse SD Card size"); + needCleanup = true; + return null; + + } else if (sdcardSize == SDCARD_NOT_SIZE_PATTERN) { + File sdcardFile = new File(sdcard); + if (sdcardFile.isFile()) { + // sdcard value is an external sdcard, so we put its path into the config.ini + values.put(AVD_INI_SDCARD_PATH, sdcard); + } else { + log.error(null, "'%1$s' is not recognized as a valid sdcard value.\n" + + "Value should be:\n" + "1. path to an sdcard.\n" + + "2. size of the sdcard to create: <size>[K|M]", sdcard); + needCleanup = true; + return null; + } } else { - // Sdcard is possibly a size. In that case we create a file called 'sdcard.img' - // in the AVD folder, and do not put any value in config.ini. - - // First, check that it matches the pattern for sdcard size - Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard); - if (m.matches()) { - // get the sdcard values for checks - try { - long sdcardSize = Long.parseLong(m.group(1)); - - // prevent overflow: no more than 999GB - // 10 digit for MiB, 13 for KiB - int digitCount = m.group(1).length(); - - String sdcardSizeModifier = m.group(2); - if ("K".equals(sdcardSizeModifier)) { - sdcardSize *= 1024L; - } else { // must be "M" per the pattern - sdcardSize *= 1024L * 1024L; - digitCount += 3; // convert the number of digit into "KiB" - } - - if (digitCount >= 13) { - log.error(null, "SD Card size is too big!"); - needCleanup = true; - return null; - } - - if (sdcardSize < 9 * 1024 * 1024) { - log.error(null, "SD Card size must be at least 9MB"); - needCleanup = true; - return null; - } - } catch (NumberFormatException e) { - // this should never happen since the string is validated - // by the regexp - log.error(null, "Unable to parse SD Card size"); - needCleanup = true; - return null; + // create the sdcard. + File sdcardFile = new File(avdFolder, SDCARD_IMG); + + boolean runMkSdcard = true; + if (sdcardFile.exists()) { + if (sdcardFile.length() == sdcardSize && editExisting) { + // There's already an sdcard file with the right size and we're + // not overriding it... so don't remove it. + runMkSdcard = false; + log.printf("SD Card already present with same size, was not changed."); } + } - // create the sdcard. - sdcardFile = new File(avdFolder, SDCARD_IMG); + if (runMkSdcard) { String path = sdcardFile.getAbsolutePath(); // execute mksdcard with the proper parameters. @@ -688,21 +656,16 @@ public final class AvdManager { } if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) { + log.error(null, "Failed to create sdcard in the AVD folder."); needCleanup = true; return null; // mksdcard output has already been displayed, no need to // output anything else. } - - // add a property containing the size of the sdcard for display purpose - // only when the dev does 'android list avd' - values.put(AVD_INI_SDCARD_SIZE, sdcard); - } else { - log.error(null, "'%1$s' is not recognized as a valid sdcard value.\n" - + "Value should be:\n" + "1. path to an sdcard.\n" - + "2. size of the sdcard to create: <size>[K|M]", sdcard); - needCleanup = true; - return null; } + + // add a property containing the size of the sdcard for display purpose + // only when the dev does 'android list avd' + values.put(AVD_INI_SDCARD_SIZE, sdcard); } } @@ -757,12 +720,23 @@ public final class AvdManager { StringBuilder report = new StringBuilder(); if (target.isPlatform()) { - report.append(String.format("Created AVD '%1$s' based on %2$s", - name, target.getName())); + if (editExisting) { + report.append(String.format("Updated AVD '%1$s' based on %2$s", + avdName, target.getName())); + } else { + report.append(String.format("Created AVD '%1$s' based on %2$s", + avdName, target.getName())); + } } else { - report.append(String.format("Created AVD '%1$s' based on %2$s (%3$s)", name, - target.getName(), target.getVendor())); + if (editExisting) { + report.append(String.format("Updated AVD '%1$s' based on %2$s (%3$s)", avdName, + target.getName(), target.getVendor())); + } else { + report.append(String.format("Created AVD '%1$s' based on %2$s (%3$s)", avdName, + target.getName(), target.getVendor())); + } } + report.append(String.format(", %s processor", AvdInfo.getPrettyAbiType(abiType))); // display the chosen hardware config if (finalHardwareValues.size() > 0) { @@ -777,28 +751,31 @@ public final class AvdManager { log.printf(report.toString()); // create the AvdInfo object, and add it to the list - AvdInfo newAvdInfo = new AvdInfo(name, + AvdInfo newAvdInfo = new AvdInfo( + avdName, + iniFile, avdFolder.getAbsolutePath(), target.hashString(), - target, values); + target, abiType, values); - AvdInfo oldAvdInfo = getAvd(name, false /*validAvdOnly*/); + AvdInfo oldAvdInfo = getAvd(avdName, false /*validAvdOnly*/); synchronized (mAllAvdList) { - if (oldAvdInfo != null && removePrevious) { + if (oldAvdInfo != null && (removePrevious || editExisting)) { mAllAvdList.remove(oldAvdInfo); } mAllAvdList.add(newAvdInfo); mValidAvdList = mBrokenAvdList = null; } - if (removePrevious && + if ((removePrevious || editExisting) && newAvdInfo != null && oldAvdInfo != null && - !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) { - log.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath()); + !oldAvdInfo.getDataFolderPath().equals(newAvdInfo.getDataFolderPath())) { + log.warning("Removing previous AVD directory at %s", + oldAvdInfo.getDataFolderPath()); // Remove the old data directory - File dir = new File(oldAvdInfo.getPath()); + File dir = new File(oldAvdInfo.getDataFolderPath()); try { deleteContentOf(dir); dir.delete(); @@ -859,9 +836,9 @@ public final class AvdManager { * is not empty. If the image folder is empty or does not exist, <code>null</code> is returned. * @throws InvalidTargetPathException if the target image folder is not in the current SDK. */ - private String getImageRelativePath(IAndroidTarget target) + private String getImageRelativePath(IAndroidTarget target, String abiType) throws InvalidTargetPathException { - String imageFullPath = target.getPath(IAndroidTarget.IMAGES); + String imageFullPath = target.getImagePath(abiType); // make this path relative to the SDK location String sdkLocation = mSdkManager.getLocation(); @@ -957,13 +934,27 @@ public final class AvdManager { * @param name of the AVD. * @param avdFolder path for the data folder of the AVD. * @param target of the AVD. + * @param removePrevious True if an existing ini file should be removed. * @throws AndroidLocationException if there's a problem getting android root directory. * @throws IOException if {@link File#getAbsolutePath()} fails. */ - private File createAvdIniFile(String name, File avdFolder, IAndroidTarget target) + private File createAvdIniFile(String name, + File avdFolder, + IAndroidTarget target, + boolean removePrevious) throws AndroidLocationException, IOException { + File iniFile = AvdInfo.getDefaultIniFile(this, name); + + if (removePrevious) { + if (iniFile.isFile()) { + iniFile.delete(); + } else if (iniFile.isDirectory()) { + deleteContentOf(iniFile); + iniFile.delete(); + } + } + HashMap<String, String> values = new HashMap<String, String>(); - File iniFile = AvdInfo.getIniFile(name); values.put(AVD_INFO_PATH, avdFolder.getAbsolutePath()); values.put(AVD_INFO_TARGET, target.hashString()); writeIniFile(iniFile, values); @@ -979,7 +970,10 @@ public final class AvdManager { * @throws IOException if {@link File#getAbsolutePath()} fails. */ private File createAvdIniFile(AvdInfo info) throws AndroidLocationException, IOException { - return createAvdIniFile(info.getName(), new File(info.getPath()), info.getTarget()); + return createAvdIniFile(info.getName(), + new File(info.getDataFolderPath()), + info.getTarget(), + false /*removePrevious*/); } /** @@ -1010,7 +1004,7 @@ public final class AvdManager { } } - String path = avdInfo.getPath(); + String path = avdInfo.getDataFolderPath(); if (path != null) { f = new File(path); if (f.exists()) { @@ -1032,8 +1026,6 @@ public final class AvdManager { return true; } - } catch (AndroidLocationException e) { - log.error(e, null); } catch (IOException e) { log.error(e, null); } catch (SecurityException e) { @@ -1060,17 +1052,25 @@ public final class AvdManager { try { if (paramFolderPath != null) { - File f = new File(avdInfo.getPath()); - log.warning("Moving '%1$s' to '%2$s'.", avdInfo.getPath(), paramFolderPath); + File f = new File(avdInfo.getDataFolderPath()); + log.warning("Moving '%1$s' to '%2$s'.", + avdInfo.getDataFolderPath(), + paramFolderPath); if (!f.renameTo(new File(paramFolderPath))) { log.error(null, "Failed to move '%1$s' to '%2$s'.", - avdInfo.getPath(), paramFolderPath); + avdInfo.getDataFolderPath(), paramFolderPath); return false; } // update AVD info - AvdInfo info = new AvdInfo(avdInfo.getName(), paramFolderPath, - avdInfo.getTargetHash(), avdInfo.getTarget(), avdInfo.getProperties()); + AvdInfo info = new AvdInfo( + avdInfo.getName(), + avdInfo.getIniFile(), + paramFolderPath, + avdInfo.getTargetHash(), + avdInfo.getTarget(), + avdInfo.getAbiType(), + avdInfo.getProperties()); replaceAvd(avdInfo, info); // update the ini file @@ -1079,7 +1079,7 @@ public final class AvdManager { if (newName != null) { File oldIniFile = avdInfo.getIniFile(); - File newIniFile = AvdInfo.getIniFile(newName); + File newIniFile = AvdInfo.getDefaultIniFile(this, newName); log.warning("Moving '%1$s' to '%2$s'.", oldIniFile.getPath(), newIniFile.getPath()); if (!oldIniFile.renameTo(newIniFile)) { @@ -1089,8 +1089,14 @@ public final class AvdManager { } // update AVD info - AvdInfo info = new AvdInfo(newName, avdInfo.getPath(), - avdInfo.getTargetHash(), avdInfo.getTarget(), avdInfo.getProperties()); + AvdInfo info = new AvdInfo( + newName, + avdInfo.getIniFile(), + avdInfo.getDataFolderPath(), + avdInfo.getTargetHash(), + avdInfo.getTarget(), + avdInfo.getAbiType(), + avdInfo.getProperties()); replaceAvd(avdInfo, info); } @@ -1135,19 +1141,20 @@ public final class AvdManager { * <p/> * This lists the $HOME/.android/avd/<name>.ini files. * Such files are properties file than then indicate where the AVD folder is located. + * <p/> + * Note: the method is to be considered private. It is made protected so that + * unit tests can easily override the AVD root. * * @return A new {@link File} array or null. The array might be empty. * @throws AndroidLocationException if there's a problem getting android root directory. */ private File[] buildAvdFilesList() throws AndroidLocationException { - // get the Android prefs location. - String avdRoot = AvdManager.getBaseAvdFolder(); + File folder = new File(getBaseAvdFolder()); // ensure folder validity. - File folder = new File(avdRoot); if (folder.isFile()) { throw new AndroidLocationException( - String.format("%1$s is not a valid folder.", avdRoot)); + String.format("%1$s is not a valid folder.", folder.getAbsolutePath())); } else if (folder.exists() == false) { // folder is not there, we create it and return folder.mkdirs(); @@ -1192,14 +1199,14 @@ public final class AvdManager { /** * Parses an AVD .ini file to create an {@link AvdInfo}. * - * @param path The path to the AVD .ini file + * @param iniPath The path to the AVD .ini file * @param log the log object to receive action logs. Cannot be null. * @return A new {@link AvdInfo} with an {@link AvdStatus} indicating whether this AVD is * valid or not. */ - private AvdInfo parseAvdInfo(File path, ISdkLog log) { + private AvdInfo parseAvdInfo(File iniPath, ISdkLog log) { Map<String, String> map = ProjectProperties.parsePropertyFile( - new FileWrapper(path), + new FileWrapper(iniPath), log); String avdPath = map.get(AVD_INFO_PATH); @@ -1227,12 +1234,20 @@ public final class AvdManager { } // get name - String name = path.getName(); - Matcher matcher = INI_NAME_PATTERN.matcher(path.getName()); + String name = iniPath.getName(); + Matcher matcher = INI_NAME_PATTERN.matcher(iniPath.getName()); if (matcher.matches()) { name = matcher.group(1); } + // get abi type + String abiType = properties == null ? null : properties.get(AVD_INI_ABI_TYPE); + // for the avds created previously without enhancement, i.e. They are created based + // on previous API Levels. They are supposed to have ARM processor type + if (abiType == null) { + abiType = SdkConstants.ABI_ARMEABI; + } + // check the image.sysdir are valid boolean validImageSysdir = true; if (properties != null) { @@ -1275,9 +1290,11 @@ public final class AvdManager { AvdInfo info = new AvdInfo( name, + iniPath, avdPath, targetHash, target, + abiType, properties, status); @@ -1489,7 +1506,7 @@ public final class AvdManager { AvdStatus status; // create the path to the new system images. - if (setImagePathProperties(avd.getTarget(), properties, log)) { + if (setImagePathProperties(avd.getTarget(), avd.getAbiType(), properties, log)) { if (properties.containsKey(AVD_INI_IMAGES_1)) { log.printf("Updated '%1$s' with value '%2$s'\n", AVD_INI_IMAGES_1, properties.get(AVD_INI_IMAGES_1)); @@ -1509,7 +1526,7 @@ public final class AvdManager { } // now write the config file - File configIniFile = new File(avd.getPath(), CONFIG_INI); + File configIniFile = new File(avd.getDataFolderPath(), CONFIG_INI); writeIniFile(configIniFile, properties); // finally create a new AvdInfo for this unbroken avd and add it to the list. @@ -1518,9 +1535,11 @@ public final class AvdManager { // FIXME: We may want to create this AvdInfo by reparsing the AVD instead. This could detect other errors. AvdInfo newAvd = new AvdInfo( avd.getName(), - avd.getPath(), + avd.getIniFile(), + avd.getDataFolderPath(), avd.getTargetHash(), avd.getTarget(), + avd.getAbiType(), properties, status); @@ -1530,13 +1549,14 @@ public final class AvdManager { /** * Sets the paths to the system images in a properties map. * @param target the target in which to find the system images. + * @param abiType the abi type of the avd to find + * the architecture-dependent system images. * @param properties the properties in which to set the paths. * @param log the log object to receive action logs. Cannot be null. * @return true if success, false if some path are missing. */ private boolean setImagePathProperties(IAndroidTarget target, - Map<String, String> properties, - ISdkLog log) { + String abiType, Map<String, String> properties, ISdkLog log) { properties.remove(AVD_INI_IMAGES_1); properties.remove(AVD_INI_IMAGES_2); @@ -1544,7 +1564,7 @@ public final class AvdManager { String property = AVD_INI_IMAGES_1; // First the image folders of the target itself - String imagePath = getImageRelativePath(target); + String imagePath = getImageRelativePath(target, abiType); if (imagePath != null) { properties.put(property, imagePath); property = AVD_INI_IMAGES_2; @@ -1554,7 +1574,7 @@ public final class AvdManager { // If the target is an add-on we need to add the Platform image as a backup. IAndroidTarget parent = target.getParent(); if (parent != null) { - imagePath = getImageRelativePath(parent); + imagePath = getImageRelativePath(parent, abiType); if (imagePath != null) { properties.put(property, imagePath); } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/DebugKeyProvider.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/DebugKeyProvider.java index bef7ccf..d31414c 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/DebugKeyProvider.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/DebugKeyProvider.java @@ -36,49 +36,49 @@ import java.security.cert.CertificateException; * <p/>This provider uses a custom keystore to create and store a key with a known password. */ public class DebugKeyProvider { - + public interface IKeyGenOutput { public void out(String message); public void err(String message); } - + private static final String PASSWORD_STRING = "android"; private static final char[] PASSWORD_CHAR = PASSWORD_STRING.toCharArray(); private static final String DEBUG_ALIAS = "AndroidDebugKey"; - + // Certificate CN value. This is a hard-coded value for the debug key. // Android Market checks against this value in order to refuse applications signed with // debug keys. private static final String CERTIFICATE_DESC = "CN=Android Debug,O=Android,C=US"; - + private KeyStore.PrivateKeyEntry mEntry; - + public static class KeytoolException extends Exception { /** default serial uid */ private static final long serialVersionUID = 1L; private String mJavaHome = null; private String mCommandLine = null; - + KeytoolException(String message) { super(message); } KeytoolException(String message, String javaHome, String commandLine) { super(message); - + mJavaHome = javaHome; mCommandLine = commandLine; } - + public String getJavaHome() { return mJavaHome; } - + public String getCommandLine() { return mCommandLine; } } - + /** * Creates a provider using a keystore at the given location. * <p/>The keystore, and a new random android debug key are created if they do not yet exist. @@ -91,16 +91,16 @@ public class DebugKeyProvider { * @param output an optional {@link IKeyGenOutput} object to get the stdout and stderr * of the keytool process call. * @throws KeytoolException If the creation of the debug key failed. - * @throws AndroidLocationException + * @throws AndroidLocationException */ public DebugKeyProvider(String osKeyStorePath, String storeType, IKeyGenOutput output) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException, IOException, KeytoolException, AndroidLocationException { - + if (osKeyStorePath == null) { osKeyStorePath = getDefaultKeyStoreOsPath(); } - + if (loadKeyEntry(osKeyStorePath, storeType) == false) { // create the store with the key createNewStore(osKeyStorePath, storeType, output); @@ -109,7 +109,7 @@ public class DebugKeyProvider { /** * Returns the OS path to the default debug keystore. - * + * * @return The OS path to the default debug keystore. * @throws KeytoolException * @throws AndroidLocationException @@ -134,7 +134,7 @@ public class DebugKeyProvider { if (mEntry != null) { return mEntry.getPrivateKey(); } - + return null; } @@ -150,7 +150,7 @@ public class DebugKeyProvider { return null; } - + /** * Loads the debug key from the keystore. * @param osKeyStorePath the OS path to the keystore. @@ -172,7 +172,7 @@ public class DebugKeyProvider { } catch (FileNotFoundException e) { return false; } - + return true; } @@ -193,9 +193,9 @@ public class DebugKeyProvider { private void createNewStore(String osKeyStorePath, String storeType, IKeyGenOutput output) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException, IOException, KeytoolException { - + if (KeystoreHelper.createNewStore(osKeyStorePath, storeType, PASSWORD_STRING, DEBUG_ALIAS, - PASSWORD_STRING, CERTIFICATE_DESC, 1 /* validity*/, output)) { + PASSWORD_STRING, CERTIFICATE_DESC, 30 /* validity*/, output)) { loadKeyEntry(osKeyStorePath, storeType); } } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java index ee2a5a6..f05e9a6 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/export/MultiApkExportHelper.java @@ -16,10 +16,10 @@ package com.android.sdklib.internal.export; +import com.android.io.FileWrapper; +import com.android.io.IAbstractFile; +import com.android.io.StreamException; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.FileWrapper; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.StreamException; import com.android.sdklib.xml.AndroidManifestParser; import com.android.sdklib.xml.ManifestData; import com.android.sdklib.xml.ManifestData.SupportsScreens; diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java index 16dd8eb..7840b91 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectCreator.java @@ -16,6 +16,7 @@ package com.android.sdklib.internal.project; +import com.android.AndroidConstants; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; @@ -325,11 +326,11 @@ public class ProjectCreator { if (isTestProject == false) { /* Make res files only for non test projects */ - File valueFolder = createDirs(resourceFolder, SdkConstants.FD_VALUES); + File valueFolder = createDirs(resourceFolder, AndroidConstants.FD_RES_VALUES); installTargetTemplate("strings.template", new File(valueFolder, "strings.xml"), keywords, target); - File layoutFolder = createDirs(resourceFolder, SdkConstants.FD_LAYOUT); + File layoutFolder = createDirs(resourceFolder, AndroidConstants.FD_RES_LAYOUT); installTargetTemplate("layout.template", new File(layoutFolder, "main.xml"), keywords, target); diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java index 19cad00..11cd277 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectProperties.java @@ -16,12 +16,12 @@ package com.android.sdklib.internal.project; +import com.android.io.FolderWrapper; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.StreamException; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.FolderWrapper; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.StreamException; import java.io.BufferedReader; import java.io.FileNotFoundException; diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java index 23cdd09..14cac2a 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/project/ProjectPropertiesWorkingCopy.java @@ -16,11 +16,11 @@ package com.android.sdklib.internal.project; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.StreamException; import com.android.sdklib.SdkConstants; import com.android.sdklib.internal.project.ProjectProperties.PropertyType; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.StreamException; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java index 3fab9ce..ba2d501 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AdbWrapper.java @@ -54,7 +54,11 @@ public class AdbWrapper { }
private void display(String format, Object...args) {
- mMonitor.setResult(format, args);
+ mMonitor.log(format, args);
+ }
+
+ private void displayError(String format, Object...args) {
+ mMonitor.logError(format, args);
}
/**
@@ -63,7 +67,7 @@ public class AdbWrapper { */
public synchronized boolean startAdb() {
if (mAdbOsLocation == null) {
- display("Error: missing path to ADB."); //$NON-NLS-1$
+ displayError("Error: missing path to ADB."); //$NON-NLS-1$
return false;
}
@@ -82,15 +86,15 @@ public class AdbWrapper { false /* waitForReaders */);
} catch (IOException ioe) {
- display("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$
+ displayError("Unable to run 'adb': %1$s.", ioe.getMessage()); //$NON-NLS-1$
// we'll return false;
} catch (InterruptedException ie) {
- display("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$
+ displayError("Unable to run 'adb': %1$s.", ie.getMessage()); //$NON-NLS-1$
// we'll return false;
}
if (status != 0) {
- display("'adb start-server' failed."); //$NON-NLS-1$
+ displayError("'adb start-server' failed."); //$NON-NLS-1$
return false;
}
@@ -105,7 +109,7 @@ public class AdbWrapper { */
public synchronized boolean stopAdb() {
if (mAdbOsLocation == null) {
- display("Error: missing path to ADB."); //$NON-NLS-1$
+ displayError("Error: missing path to ADB."); //$NON-NLS-1$
return false;
}
@@ -127,7 +131,7 @@ public class AdbWrapper { }
if (status != 0) {
- display("'adb kill-server' failed -- run manually if necessary."); //$NON-NLS-1$
+ displayError("'adb kill-server' failed -- run manually if necessary."); //$NON-NLS-1$
return false;
}
@@ -163,7 +167,7 @@ public class AdbWrapper { while (true) {
String line = errReader.readLine();
if (line != null) {
- display("ADB Error: %1$s", line);
+ displayError("ADB Error: %1$s", line);
errorOutput.add(line);
} else {
break;
@@ -185,7 +189,7 @@ public class AdbWrapper { while (true) {
String line = outReader.readLine();
if (line != null) {
- display("ADB: %1$s", line);
+ displayError("ADB: %1$s", line);
stdOutput.add(line);
} else {
break;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java index bed9174..526bfcb 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java @@ -247,7 +247,22 @@ public class AddonPackage extends Package return mLibs;
}
- /** Returns a short description for an {@link IDescription}. */
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return String.format("%1$s by %2$s%3$s",
+ getName(),
+ getVendor(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
@Override
public String getShortDescription() {
return String.format("%1$s by %2$s, Android API %3$s, revision %4$s%5$s",
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java index 9b8d808..c0b7041 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonsListFetcher.java @@ -142,11 +142,11 @@ public class AddonsListFetcher { reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
}
- monitor.setResult("Failed to fetch URL %1$s, reason: %2$s", url, reason);
+ monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
}
if (validationError[0] != null) {
- monitor.setResult("%s", validationError[0]); //$NON-NLS-1$
+ monitor.logError("%s", validationError[0]); //$NON-NLS-1$
}
// Stop here if we failed to validate the XML. We don't want to load it.
@@ -409,13 +409,13 @@ public class AddonsListFetcher { return doc;
} catch (ParserConfigurationException e) {
- monitor.setResult("Failed to create XML document builder");
+ monitor.logError("Failed to create XML document builder");
} catch (SAXException e) {
- monitor.setResult("Failed to parse XML document");
+ monitor.logError("Failed to parse XML document");
} catch (IOException e) {
- monitor.setResult("Failed to read XML document");
+ monitor.logError("Failed to read XML document");
}
return null;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java index f3fd347..5807ca3 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ArchiveInstaller.java @@ -63,7 +63,7 @@ public class ArchiveInstaller { String name = pkg.getShortDescription();
if (pkg instanceof ExtraPackage && !((ExtraPackage) pkg).isPathValid()) {
- monitor.setResult("Skipping %1$s: %2$s is not a valid install path.",
+ monitor.log("Skipping %1$s: %2$s is not a valid install path.",
name,
((ExtraPackage) pkg).getPath());
return false;
@@ -71,14 +71,14 @@ public class ArchiveInstaller { if (archive.isLocal()) {
// This should never happen.
- monitor.setResult("Skipping already installed archive: %1$s for %2$s",
+ monitor.log("Skipping already installed archive: %1$s for %2$s",
name,
archive.getOsDescription());
return false;
}
if (!archive.isCompatible()) {
- monitor.setResult("Skipping incompatible archive: %1$s for %2$s",
+ monitor.log("Skipping incompatible archive: %1$s for %2$s",
name,
archive.getOsDescription());
return false;
@@ -88,7 +88,7 @@ public class ArchiveInstaller { if (archiveFile != null) {
// Unarchive calls the pre/postInstallHook methods.
if (unarchive(archive, osSdkRoot, archiveFile, sdkManager, monitor)) {
- monitor.setResult("Installed %1$s", name);
+ monitor.log("Installed %1$s", name);
// Delete the temp archive if it exists, only on success
OsHelper.deleteFileOrFolder(archiveFile);
return true;
@@ -110,7 +110,7 @@ public class ArchiveInstaller { String name = archive.getParentPackage().getShortDescription();
String desc = String.format("Downloading %1$s", name);
monitor.setDescription(desc);
- monitor.setResult(desc);
+ monitor.log(desc);
String link = archive.getUrl();
if (!link.startsWith("http://") //$NON-NLS-1$
@@ -120,7 +120,7 @@ public class ArchiveInstaller { Package pkg = archive.getParentPackage();
SdkSource src = pkg.getParentSource();
if (src == null) {
- monitor.setResult("Internal error: no source for archive %1$s", name);
+ monitor.logError("Internal error: no source for archive %1$s", name);
return null;
}
@@ -152,7 +152,7 @@ public class ArchiveInstaller { OsHelper.deleteFileOrFolder(tmpFolder);
}
if (!tmpFolder.mkdirs()) {
- monitor.setResult("Failed to create directory %1$s", tmpFolder.getPath());
+ monitor.logError("Failed to create directory %1$s", tmpFolder.getPath());
return null;
}
}
@@ -161,7 +161,7 @@ public class ArchiveInstaller { // if the file exists, check its checksum & size. Use it if complete
if (tmpFile.exists()) {
if (tmpFile.length() == archive.getSize()) {
- String chksum = "";
+ String chksum = ""; //$NON-NLS-1$
try {
chksum = fileChecksum(archive.getChecksumType().getMessageDigest(),
tmpFile,
@@ -214,10 +214,10 @@ public class ArchiveInstaller { } catch (FileNotFoundException e) {
// The FNF message is just the URL. Make it a bit more useful.
- monitor.setResult("File not found: %1$s", e.getMessage());
+ monitor.logError("File not found: %1$s", e.getMessage());
} catch (Exception e) {
- monitor.setResult(e.getMessage());
+ monitor.logError(e.getMessage());
} finally {
if (is != null) {
@@ -327,14 +327,15 @@ public class ArchiveInstaller { }
if (monitor.isCancelRequested()) {
- monitor.setResult("Download aborted by user at %1$d bytes.", total);
+ monitor.log("Download aborted by user at %1$d bytes.", total);
return false;
}
}
if (total != size) {
- monitor.setResult("Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
+ monitor.logError(
+ "Download finished with wrong size. Expected %1$d bytes, got %2$d bytes.",
size, total);
return false;
}
@@ -343,7 +344,7 @@ public class ArchiveInstaller { String actual = getDigestChecksum(digester);
String expected = archive.getChecksum();
if (!actual.equalsIgnoreCase(expected)) {
- monitor.setResult("Download finished with wrong checksum. Expected %1$s, got %2$s.",
+ monitor.logError("Download finished with wrong checksum. Expected %1$s, got %2$s.",
expected, actual);
return false;
}
@@ -352,10 +353,10 @@ public class ArchiveInstaller { } catch (FileNotFoundException e) {
// The FNF message is just the URL. Make it a bit more useful.
- monitor.setResult("File not found: %1$s", e.getMessage());
+ monitor.logError("File not found: %1$s", e.getMessage());
} catch (Exception e) {
- monitor.setResult(e.getMessage());
+ monitor.logError(e.getMessage());
} finally {
if (os != null) {
@@ -391,7 +392,7 @@ public class ArchiveInstaller { String pkgName = pkg.getShortDescription();
String pkgDesc = String.format("Installing %1$s", pkgName);
monitor.setDescription(pkgDesc);
- monitor.setResult(pkgDesc);
+ monitor.log(pkgDesc);
// Ideally we want to always unzip in a temp folder which name depends on the package
// type (e.g. addon, tools, etc.) and then move the folder to the destination folder.
@@ -432,12 +433,12 @@ public class ArchiveInstaller { if (destFolder == null) {
// this should not seriously happen.
- monitor.setResult("Failed to compute installation directory for %1$s.", pkgName);
+ monitor.log("Failed to compute installation directory for %1$s.", pkgName);
return false;
}
if (!pkg.preInstallHook(archive, monitor, osSdkRoot, destFolder)) {
- monitor.setResult("Skipping archive: %1$s", pkgName);
+ monitor.log("Skipping archive: %1$s", pkgName);
return false;
}
@@ -450,14 +451,14 @@ public class ArchiveInstaller { }
if (oldDestFolder == null) {
// this should not seriously happen.
- monitor.setResult("Failed to find a temp directory in %1$s.", osSdkRoot);
+ monitor.logError("Failed to find a temp directory in %1$s.", osSdkRoot);
return false;
}
// Try to move the current dest dir to the temp/old one. Tell the user if it failed.
while(true) {
if (!moveFolder(destFolder, oldDestFolder)) {
- monitor.setResult("Failed to rename directory %1$s to %2$s.",
+ monitor.logError("Failed to rename directory %1$s to %2$s.",
destFolder.getPath(), oldDestFolder.getPath());
if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
@@ -489,7 +490,7 @@ public class ArchiveInstaller { // -2- Unzip new content directly in place.
if (!destFolder.mkdirs()) {
- monitor.setResult("Failed to create directory %1$s", destFolder.getPath());
+ monitor.logError("Failed to create directory %1$s", destFolder.getPath());
return false;
}
@@ -498,7 +499,7 @@ public class ArchiveInstaller { }
if (!generateSourceProperties(archive, destFolder)) {
- monitor.setResult("Failed to generate source.properties in directory %1$s",
+ monitor.logError("Failed to generate source.properties in directory %1$s",
destFolder.getPath());
return false;
}
@@ -627,7 +628,7 @@ public class ArchiveInstaller { // Create directory if it doesn't exist yet. This allows us to create
// empty directories.
if (!destFile.isDirectory() && !destFile.mkdirs()) {
- monitor.setResult("Failed to create temp directory %1$s",
+ monitor.logError("Failed to create temp directory %1$s",
destFile.getPath());
return false;
}
@@ -638,7 +639,7 @@ public class ArchiveInstaller { File parentDir = destFile.getParentFile();
if (!parentDir.isDirectory()) {
if (!parentDir.mkdirs()) {
- monitor.setResult("Failed to create temp directory %1$s",
+ monitor.logError("Failed to create temp directory %1$s",
parentDir.getPath());
return false;
}
@@ -689,7 +690,7 @@ public class ArchiveInstaller { return true;
} catch (IOException e) {
- monitor.setResult("Unzip failed: %1$s", e.getMessage());
+ monitor.logError("Unzip failed: %1$s", e.getMessage());
} finally {
if (zipFile != null) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java index 1629045..ca6f463 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java @@ -99,7 +99,19 @@ public class BrokenPackage extends Package return mExactApiLevel;
}
- /** Returns a short description for an {@link IDescription}. */
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return mShortDescription;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
@Override
public String getShortDescription() {
return mShortDescription;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java index 8a4c19d..5171454 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java @@ -117,13 +117,35 @@ public class DocPackage extends Package implements IPackageVersion { mVersion.saveProperties(props);
}
- /** Returns the version, for platform, add-on and doc packages.
- * Can be 0 if this is a local package of unknown api-level. */
+ /**
+ * Returns the version, for platform, add-on and doc packages.
+ * Can be 0 if this is a local package of unknown api-level.
+ */
public AndroidVersion getVersion() {
return mVersion;
}
- /** Returns a short description for an {@link IDescription}. */
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ if (mVersion.isPreview()) {
+ return String.format("Documentation for Android '%1$s' Preview SDK%2$s",
+ mVersion.getCodename(),
+ isObsolete() ? " (Obsolete)" : "");
+ } else {
+ return String.format("Documentation for Android SDK%2$s",
+ mVersion.getApiLevel(),
+ isObsolete() ? " (Obsolete)" : "");
+ }
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
@Override
public String getShortDescription() {
if (mVersion.isPreview()) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java index 49236dc..bdf2805 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java @@ -253,9 +253,7 @@ public class ExtraPackage extends MinToolsPackage return ""; //$NON-NLS-1$
}
- /** Returns a short description for an {@link IDescription}. */
- @Override
- public String getShortDescription() {
+ private String getPrettyName() {
String name = mPath;
// In the past, we used to save the extras in a folder vendor-path,
@@ -299,8 +297,31 @@ public class ExtraPackage extends MinToolsPackage name = name.replaceAll(" Usb ", " USB "); //$NON-NLS-1$
name = name.replaceAll(" Api ", " API "); //$NON-NLS-1$
+ return name;
+ }
+
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ String s = String.format("%1$s package%2$s",
+ getPrettyName(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+
+ return s;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
+ @Override
+ public String getShortDescription() {
+
String s = String.format("%1$s package, revision %2$d%3$s",
- name,
+ getPrettyName(),
getRevision(),
isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
@@ -394,19 +415,33 @@ public class ExtraPackage extends MinToolsPackage ExtraPackage ep = (ExtraPackage) pkg;
// To be backward compatible, we need to support the old vendor-path form
- if (ep.mPath != null && (ep.mVendor == null || ep.mVendor.length() == 0) &&
- mPath != null && mVendor != null) {
- if (ep.mPath.equals(mVendor + "-" + mPath)) { //$NON-NLS-1$
+ // in either the current or the remote package.
+ //
+ // The vendor test below needs to account for an old installed package
+ // (e.g. with an install path of vendor-name) that has then beeen updated
+ // in-place and thus when reloaded contains the vendor name in both the
+ // path and the vendor attributes.
+ if (ep.mPath != null && mPath != null && mVendor != null) {
+ if (ep.mPath.equals(mVendor + "-" + mPath) && //$NON-NLS-1$
+ (ep.mVendor == null || ep.mVendor.length() == 0
+ || ep.mVendor.equals(mVendor))) {
+ return true;
+ }
+ }
+ if (mPath != null && ep.mPath != null && ep.mVendor != null) {
+ if (mPath.equals(ep.mVendor + "-" + ep.mPath) && //$NON-NLS-1$
+ (mVendor == null || mVendor.length() == 0 || mVendor.equals(ep.mVendor))) {
return true;
}
}
+
if (!mPath.equals(ep.mPath)) {
return false;
}
if ((mVendor == null && ep.mVendor == null) ||
- (mVendor != null && !mVendor.equals(ep.mVendor))) {
- return false;
+ (mVendor != null && mVendor.equals(ep.mVendor))) {
+ return true;
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java index e08e27c..40f1ddb 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ITaskMonitor.java @@ -27,6 +27,21 @@ package com.android.sdklib.internal.repository; * If the task runs in a non-UI worker thread, the task factory implementation
* will take care of the update the UI in the correct thread. The task itself
* must not have to deal with it.
+ * <p/>
+ * A monitor typically has 3 levels of text displayed: <br/>
+ * - A <b>title</b> <em>may</em> be present on a task dialog, typically when a task
+ * dialog is created. This is not covered by this monitor interface. <br/>
+ * - A <b>description</b> displays prominent information on what the task
+ * is currently doing. This is expected to vary over time, typically changing
+ * with each sub-monitor, and typically only the last description is visible.
+ * For example an updater would typically have descriptions such as "downloading",
+ * "installing" and finally "done". This is set using {@link #setDescription}. <br/>
+ * - A <b>verbose</b> optional log that can provide more information than the summary
+ * description and is typically displayed in some kind of scrollable multi-line
+ * text field so that the user can keep track of what happened. 3 levels are
+ * provided: error, normal and verbose. An UI may hide the log till an error is
+ * logged and/or might hide the verbose text unless a flag is checked by the user.
+ * This is set using {@link #log}, {@link #logError} and {@link #logVerbose}.
*/
public interface ITaskMonitor {
@@ -34,13 +49,26 @@ public interface ITaskMonitor { * Sets the description in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
- public void setDescription(String descriptionFormat, Object...args);
+ public void setDescription(String format, Object...args);
+
+ /**
+ * Logs a "normal" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void log(String format, Object...args);
+
+ /**
+ * Logs an "error" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logError(String format, Object...args);
/**
- * Sets the result text in the current task dialog.
+ * Logs a "verbose" information line, that is extra details which are typically
+ * not that useful for the end-user and might be hidden until explicitly shown.
* This method can be invoked from a non-UI thread.
*/
- public void setResult(String resultFormat, Object...args);
+ public void logVerbose(String format, Object...args);
/**
* Sets the max value of the progress bar.
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java index eb00562..c4b92b5 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java @@ -303,8 +303,12 @@ public class LocalSdkParser { names.add(file.getName());
}
}
+
+ final String emulatorBinName =
+ SdkConstants.FN_EMULATOR + SdkConstants.FN_EMULATOR_EXTENSION;
+
if (!names.contains(SdkConstants.androidCmdName()) ||
- !names.contains(SdkConstants.FN_EMULATOR)) {
+ !names.contains(emulatorBinName)) {
return null;
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java index 02cebe1..c2338f3 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/OsHelper.java @@ -22,6 +22,8 @@ import java.io.File; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
/**
@@ -30,6 +32,21 @@ import java.io.IOException; abstract class OsHelper {
/**
+ * Reflection method for File.setExecutable(boolean, boolean). Only present in Java 6.
+ */
+ private static Method sFileSetExecutable = null;
+ /**
+ * Whether File.setExecutable was queried through reflection. This is to only
+ * attempt reflection once.
+ */
+ private static boolean sFileReflectionDone = false;
+ /**
+ * Parameters to call File.setExecutable through reflection.
+ */
+ private final static Object[] sFileSetExecutableParams = new Object[] {
+ Boolean.TRUE, Boolean.FALSE };
+
+ /**
* Helper to delete a file or a directory.
* For a directory, recursively deletes all of its content.
* Files that cannot be deleted right away are marked for deletion on exit.
@@ -76,18 +93,49 @@ abstract class OsHelper { }
/**
- * Sets the executable Unix permission (0777) on a file or folder.
+ * Sets the executable Unix permission (+x) on a file or folder.
+ * <p/>
+ * This attempts to use {@link File#setExecutable(boolean, boolean)} through reflection if
+ * it's available.
+ * If this is not available, this invokes a chmod exec instead,
+ * so there is no guarantee of it being fast.
* <p/>
- * This invokes a chmod exec, so there is no guarantee of it being fast.
* Caller must make sure to not invoke this under Windows.
*
* @param file The file to set permissions on.
* @throws IOException If an I/O error occurs
*/
static void setExecutablePermission(File file) throws IOException {
+ if (sFileReflectionDone == false) {
+ try {
+ sFileSetExecutable = File.class.getMethod("setExecutable", //$NON-NLS-1$
+ boolean.class, boolean.class);
+
+ } catch (SecurityException e) {
+ // do nothing we'll use chdmod instead
+ } catch (NoSuchMethodException e) {
+ // do nothing we'll use chdmod instead
+ }
+
+ sFileReflectionDone = true;
+ }
+
+ if (sFileSetExecutable != null) {
+ try {
+ sFileSetExecutable.invoke(file, sFileSetExecutableParams);
+ return;
+ } catch (IllegalArgumentException e) {
+ // we'll run chmod below
+ } catch (IllegalAccessException e) {
+ // we'll run chmod below
+ } catch (InvocationTargetException e) {
+ // we'll run chmod below
+ }
+ }
+
Runtime.getRuntime().exec(new String[] {
- "chmod", "777", file.getAbsolutePath()
- });
+ "chmod", "+x", file.getAbsolutePath() //$NON-NLS-1$ //$NON-NLS-2$
+ });
}
/**
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java index 58be3c9..c597ad8 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java @@ -372,6 +372,17 @@ public abstract class Package implements IDescription, Comparable<Package> { }
/**
+ * Returns a description of this package that is suitable for a list display.
+ * Should not be empty. Must never be null.
+ * <p/>
+ * Note that this is the "base" name for the package
+ * with no specific revision nor API mentionned.
+ * In contrast, {@link #getShortDescription()} should be used if you want more details
+ * such as the package revision number or the API, if applicable.
+ */
+ public abstract String getListDescription();
+
+ /**
* Returns a short description for an {@link IDescription}.
* Can be empty but not null.
*/
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java index c303e2f..622a922 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java @@ -121,7 +121,31 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion return mVersion;
}
- /** Returns a short description for an {@link IDescription}. */
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ String s;
+
+ if (mVersion.isPreview()) {
+ s = String.format("SDK Platform Android %1$s Preview%2$s",
+ getVersionName(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+ } else {
+ s = String.format("SDK Platform Android %1$s%2$s",
+ getVersionName(),
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
+ }
+
+ return s;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
@Override
public String getShortDescription() {
String s;
@@ -130,13 +154,13 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion s = String.format("SDK Platform Android %1$s Preview, revision %2$s%3$s",
getVersionName(),
getRevision(),
- isObsolete() ? " (Obsolete)" : "");
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
} else {
s = String.format("SDK Platform Android %1$s, API %2$d, revision %3$s%4$s",
getVersionName(),
mVersion.getApiLevel(),
getRevision(),
- isObsolete() ? " (Obsolete)" : "");
+ isObsolete() ? " (Obsolete)" : ""); //$NON-NLS-2$
}
return s;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java index 860d703..9a2de62 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java @@ -143,7 +143,20 @@ public class PlatformToolPackage extends Package { archiveOsPath); } - /** Returns a short description for an {@link IDescription}. */ + /** + * Returns a description of this package that is suitable for a list display. + * <p/> + * {@inheritDoc} + */ + @Override + public String getListDescription() { + return String.format("Android SDK Platform-tools%1$s", + isObsolete() ? " (Obsolete)" : ""); + } + + /** + * Returns a short description for an {@link IDescription}. + */ @Override public String getShortDescription() { return String.format("Android SDK Platform-tools, revision %1$d%2$s", diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java index 035677b..b436b91 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java @@ -175,7 +175,23 @@ public class SamplePackage extends MinToolsPackage return mVersion;
}
- /** Returns a short description for an {@link IDescription}. */
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ String s = String.format("Samples for SDK API %1$s%2$s%3$s",
+ mVersion.getApiString(),
+ mVersion.isPreview() ? " Preview" : "",
+ isObsolete() ? " (Obsolete)" : "");
+ return s;
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
@Override
public String getShortDescription() {
String s = String.format("Samples for SDK API %1$s%2$s, revision %3$d%4$s",
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java index 58bf314..22b9c42 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSource.java @@ -57,7 +57,7 @@ import javax.xml.validation.Validator; * It may be a full repository or an add-on only repository.
* A repository describes one or {@link Package}s available for download.
*/
-public abstract class SdkSource implements IDescription {
+public abstract class SdkSource implements IDescription, Comparable<SdkSource> {
private String mUrl;
@@ -151,6 +151,14 @@ public abstract class SdkSource implements IDescription { }
/**
+ * Implementation of the {@link Comparable} interface.
+ * Simply compares the URL using the string's default ordering.
+ */
+ public int compareTo(SdkSource rhs) {
+ return this.getUrl().compareTo(rhs.getUrl());
+ }
+
+ /**
* Returns the UI-visible name of the source. Can be null.
*/
public String getUiName() {
@@ -250,7 +258,7 @@ public abstract class SdkSource implements IDescription { url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
}
- monitor.setDescription("Fetching %1$s", url);
+ monitor.setDescription("Fetching URL: %1$s", url);
monitor.incProgress(1);
mFetchError = null;
@@ -276,7 +284,7 @@ public abstract class SdkSource implements IDescription { }
if (xml != null) {
- monitor.setDescription("Validate XML");
+ monitor.setDescription(String.format("Validate XML: %1$s", url));
for (int tryOtherUrl = 0; tryOtherUrl < 2; tryOtherUrl++) {
// Explore the XML to find the potential XML schema version
@@ -295,7 +303,7 @@ public abstract class SdkSource implements IDescription { if (usingAlternateUrl && validatedDoc != null) {
// If the second tentative succeeded, indicate it in the console
// with the URL that worked.
- monitor.setResult("Repository found at %1$s", url);
+ monitor.log("Repository found at %1$s", url);
// Keep the modified URL
mUrl = url;
@@ -380,11 +388,11 @@ public abstract class SdkSource implements IDescription { reason = String.format("Unknown (%1$s)", exception[0].getClass().getName());
}
- monitor.setResult("Failed to fetch URL %1$s, reason: %2$s", url, reason);
+ monitor.logError("Failed to fetch URL %1$s, reason: %2$s", url, reason);
}
if (validationError[0] != null) {
- monitor.setResult("%s", validationError[0]); //$NON-NLS-1$
+ monitor.logError("%s", validationError[0]); //$NON-NLS-1$
}
// Stop here if we failed to validate the XML. We don't want to load it.
@@ -424,7 +432,7 @@ public abstract class SdkSource implements IDescription { monitor.incProgress(1);
if (xml != null) {
- monitor.setDescription("Parse XML");
+ monitor.setDescription(String.format("Parse XML: %1$s", url));
monitor.incProgress(1);
parsePackages(validatedDoc, validatedUri, monitor);
if (mPackages == null || mPackages.length == 0) {
@@ -740,12 +748,11 @@ public abstract class SdkSource implements IDescription { if (p != null) {
packages.add(p);
- monitor.setDescription("Found %1$s", p.getShortDescription());
+ monitor.logVerbose("Found %1$s", p.getShortDescription());
}
} catch (Exception e) {
// Ignore invalid packages
- monitor.setResult("Ignoring invalid %1$s element: %2$s",
- name, e.toString());
+ monitor.logError("Ignoring invalid %1$s element: %2$s", name, e.toString());
}
}
}
@@ -794,13 +801,13 @@ public abstract class SdkSource implements IDescription { return doc;
} catch (ParserConfigurationException e) {
- monitor.setResult("Failed to create XML document builder");
+ monitor.logError("Failed to create XML document builder");
} catch (SAXException e) {
- monitor.setResult("Failed to parse XML document");
+ monitor.logError("Failed to parse XML document");
} catch (IOException e) {
- monitor.setResult("Failed to read XML document");
+ monitor.logError("Failed to read XML document");
}
return null;
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java index 22678b3..be99f22 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SdkSources.java @@ -111,7 +111,7 @@ public class SdkSources { }
/**
- * Returns an array of sources attached to the given category.
+ * Returns a new array of sources attached to the given category.
* Might return an empty array, but never returns null.
*/
public SdkSource[] getSources(SdkSourceCategory category) {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java index 6e53dd7..69039ea 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java @@ -151,7 +151,20 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency return mMinPlatformToolsRevision;
}
- /** Returns a short description for an {@link IDescription}. */
+ /**
+ * Returns a description of this package that is suitable for a list display.
+ * <p/>
+ * {@inheritDoc}
+ */
+ @Override
+ public String getListDescription() {
+ return String.format("Android SDK Tools%1$s",
+ isObsolete() ? " (Obsolete)" : "");
+ }
+
+ /**
+ * Returns a short description for an {@link IDescription}.
+ */
@Override
public String getShortDescription() {
return String.format("Android SDK Tools, revision %1$d%2$s",
@@ -250,17 +263,17 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency status = grabProcessOutput(proc, monitor, scriptName);
} catch (Exception e) {
- monitor.setResult("Exception: %s", e.toString());
+ monitor.logError("Exception: %s", e.toString());
}
if (status != 0) {
- monitor.setResult("Failed to execute %s", scriptName);
+ monitor.logError("Failed to execute %s", scriptName);
return;
}
}
/**
- * Get the stderr/stdout outputs of a process and return when the process is done.
+ * Gets the stderr/stdout outputs of a process and returns when the process is done.
* Both <b>must</b> be read or the process will block on windows.
* @param process The process to get the ouput from.
* @param monitor The monitor where to output errors.
@@ -285,7 +298,7 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency while (true) {
String line = errReader.readLine();
if (line != null) {
- monitor.setResult("[%1$s] Error: %2$s", scriptName, line);
+ monitor.logError("[%1$s] Error: %2$s", scriptName, line);
} else {
break;
}
@@ -306,7 +319,7 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency while (true) {
String line = outReader.readLine();
if (line != null) {
- monitor.setResult("[%1$s] %2$s", scriptName, line);
+ monitor.log("[%1$s] %2$s", scriptName, line);
} else {
break;
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java index 2cb6ace..26fcd7b 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifest.java @@ -16,10 +16,10 @@ package com.android.sdklib.xml; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.StreamException; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.StreamException; import org.w3c.dom.Node; import org.xml.sax.InputSource; @@ -76,6 +76,7 @@ public final class AndroidManifest { public final static String ATTRIBUTE_REQ_HARDKEYBOARD = "reqHardKeyboard"; public final static String ATTRIBUTE_REQ_KEYBOARDTYPE = "reqKeyboardType"; public final static String ATTRIBUTE_REQ_TOUCHSCREEN = "reqTouchScreen"; + public static final String ATTRIBUTE_THEME = "theme"; /** * Returns an {@link IAbstractFile} object representing the manifest for the given project. diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java index 09e81f7..ca59a8e 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/xml/AndroidManifestParser.java @@ -16,13 +16,13 @@ package com.android.sdklib.xml; +import com.android.io.IAbstractFile; +import com.android.io.IAbstractFolder; +import com.android.io.StreamException; import com.android.resources.Keyboard; import com.android.resources.Navigation; import com.android.resources.TouchScreen; import com.android.sdklib.SdkConstants; -import com.android.sdklib.io.IAbstractFile; -import com.android.sdklib.io.IAbstractFolder; -import com.android.sdklib.io.StreamException; import com.android.sdklib.xml.ManifestData.Activity; import com.android.sdklib.xml.ManifestData.Instrumentation; import com.android.sdklib.xml.ManifestData.SupportsScreens; diff --git a/sdkmanager/libs/sdklib/tests/Android.mk b/sdkmanager/libs/sdklib/tests/Android.mk new file mode 100644 index 0000000..0179dc1 --- /dev/null +++ b/sdkmanager/libs/sdklib/tests/Android.mk @@ -0,0 +1,28 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +LOCAL_MODULE := sdklib-tests +LOCAL_MODULE_TAGS := optional + +LOCAL_JAVA_LIBRARIES := sdklib junit + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/build/DebugKeyProviderTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/build/DebugKeyProviderTest.java new file mode 100755 index 0000000..6f6896e --- /dev/null +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/build/DebugKeyProviderTest.java @@ -0,0 +1,125 @@ +/* + * 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.sdklib.internal.build; + +import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput; + +import java.io.File; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Date; + +import junit.framework.TestCase; + +public class DebugKeyProviderTest extends TestCase { + + private File mTmpFile; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // We want to allocate a new tmp file but not have it actually exist + mTmpFile = File.createTempFile(this.getClass().getSimpleName(), ".keystore"); + assertTrue(mTmpFile.delete()); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + if (mTmpFile != null) { + if (!mTmpFile.delete()) { + mTmpFile.deleteOnExit(); + } + mTmpFile = null; + } + } + + public void testCreateAndCheckKey() throws Exception { + String osPath = mTmpFile.getAbsolutePath(); + + KeygenOutput keygenOutput = new KeygenOutput(); + + // "now" is just slightly before the key was created + long now = System.currentTimeMillis(); + + DebugKeyProvider provider; + try { + provider = new DebugKeyProvider(osPath, null /*storeType*/, keygenOutput); + } catch (Throwable t) { + // In case we get any kind of exception, rewrap it to make sure we output + // the path used. + String msg = String.format("%1$s in %2$s\n%3$s", + t.getClass().getSimpleName(), osPath, t.toString()); + throw new Exception(msg, t); + } + assertNotNull(provider); + + assertEquals("", keygenOutput.getOut()); + assertEquals("", keygenOutput.getErr()); + + PrivateKey key = provider.getDebugKey(); + assertNotNull(key); + + X509Certificate certificate = (X509Certificate) provider.getCertificate(); + assertNotNull(certificate); + + // The "not-after" (a.k.a. expiration) date should be after "now" + Calendar c = Calendar.getInstance(); + c.setTimeInMillis(now); + assertTrue(certificate.getNotAfter().compareTo(c.getTime()) > 0); + + // It should be valid after 1 year from now (adjust by a second since the 'now' time + // doesn't exactly match the creation time... 1 second should be enough.) + c.add(Calendar.DAY_OF_YEAR, 365); + c.add(Calendar.SECOND, -1); + assertTrue("1 year expiration failed", + certificate.getNotAfter().compareTo(c.getTime()) > 0); + + // and 30 years from now + c.add(Calendar.DAY_OF_YEAR, 29 * 365); + assertTrue("30 year expiration failed", + certificate.getNotAfter().compareTo(c.getTime()) > 0); + + // however expiration date should be passed in 30 years + 1 hour + c.add(Calendar.HOUR, 1); + assertFalse("30 year and 1 hour expiration failed", + certificate.getNotAfter().compareTo(c.getTime()) > 0); + } + + private static class KeygenOutput implements IKeyGenOutput { + private String mOut = ""; //$NON-NLS-1$ + private String mErr = ""; //$NON-NLS-1$ + + public void out(String message) { + mOut += message + "\n"; //$NON-NLS-1$ + } + + public void err(String message) { + mErr += message + "\n"; //$NON-NLS-1$ + } + + public String getOut() { + return mOut; + } + + public String getErr() { + return mErr; + } + } +} diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/AddonsListFetcherTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/AddonsListFetcherTest.java index 3f2bb84..9cf84a2 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/AddonsListFetcherTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/AddonsListFetcherTest.java @@ -104,7 +104,9 @@ public class AddonsListFetcherTest extends TestCase { Site[] result = mFetcher._parseAddonsList(doc, uri, monitor);
assertEquals("", monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedResults());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
+ assertEquals("", monitor.getCapturedVerboseLog());
// check the sites we found...
assertEquals(3, result.length);
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/BrokenPackageTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/BrokenPackageTest.java index 3e9ba8d..3e9ba8d 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/BrokenPackageTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/BrokenPackageTest.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockAddonPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java index e99463b..55ace17 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockAddonPackage.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockAddonPackage.java @@ -18,6 +18,7 @@ package com.android.sdklib.internal.repository; import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
import java.util.Map;
@@ -68,6 +69,14 @@ public class MockAddonPackage extends AddonPackage { return "mock addon target";
}
+ public String[] getAbiList() {
+ return new String[] { SdkConstants.ABI_ARMEABI };
+ }
+
+ public String getImagePath(String abiType) {
+ return SdkConstants.OS_IMAGES_FOLDER;
+ }
+
public String getLocation() {
return "";
}
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockBrokenPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockBrokenPackage.java index 289305b..289305b 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockBrokenPackage.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockBrokenPackage.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockEmptySdkManager.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockEmptySdkManager.java index f66734b..f66734b 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockEmptySdkManager.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockEmptySdkManager.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockMonitor.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockMonitor.java index 56a7c6c..3b7e608 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockMonitor.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockMonitor.java @@ -24,26 +24,44 @@ package com.android.sdklib.internal.repository; */
public class MockMonitor implements ITaskMonitor {
- String mCapturedResults = "";
- String mCapturedDescriptions = "";
+ String mCapturedLog = ""; //$NON-NLS-1$
+ String mCapturedErrorLog = ""; //$NON-NLS-1$
+ String mCapturedVerboseLog = ""; //$NON-NLS-1$
+ String mCapturedDescriptions = ""; //$NON-NLS-1$
- public String getCapturedResults() {
- return mCapturedResults;
+ public String getCapturedLog() {
+ return mCapturedLog;
+ }
+
+ public String getCapturedErrorLog() {
+ return mCapturedErrorLog;
+ }
+
+ public String getCapturedVerboseLog() {
+ return mCapturedVerboseLog;
}
public String getCapturedDescriptions() {
return mCapturedDescriptions;
}
- public void setResult(String resultFormat, Object... args) {
- mCapturedResults += String.format(resultFormat, args) + "\n";
+ public void log(String format, Object... args) {
+ mCapturedLog += String.format(format, args) + "\n"; //$NON-NLS-1$
+ }
+
+ public void logError(String format, Object... args) {
+ mCapturedErrorLog += String.format(format, args) + "\n"; //$NON-NLS-1$
+ }
+
+ public void logVerbose(String format, Object... args) {
+ mCapturedVerboseLog += String.format(format, args) + "\n"; //$NON-NLS-1$
}
public void setProgressMax(int max) {
}
- public void setDescription(String descriptionFormat, Object... args) {
- mCapturedDescriptions += String.format(descriptionFormat, args) + "\n";
+ public void setDescription(String format, Object... args) {
+ mCapturedDescriptions += String.format(format, args) + "\n"; //$NON-NLS-1$
}
public boolean isCancelRequested() {
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java index 0a58487..ad1ab16 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformPackage.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformPackage.java @@ -18,6 +18,7 @@ package com.android.sdklib.internal.repository; import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.SdkConstants;
import java.util.Map;
import java.util.Properties;
@@ -100,6 +101,14 @@ public class MockPlatformPackage extends PlatformPackage { return "mock platform target";
}
+ public String[] getAbiList() {
+ return new String[] { SdkConstants.ABI_ARMEABI };
+ }
+
+ public String getImagePath(String abiType) {
+ return SdkConstants.OS_IMAGES_FOLDER;
+ }
+
public String getLocation() {
return "";
}
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java index 0befa80..0befa80 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockPlatformToolPackage.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockPlatformToolPackage.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockToolPackage.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java index 8ce704c..8ce704c 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/MockToolPackage.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/MockToolPackage.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/SdkAddonSourceTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/SdkAddonSourceTest.java index ea6c4f6..6df440e 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/SdkAddonSourceTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/SdkAddonSourceTest.java @@ -176,8 +176,9 @@ public class SdkAddonSourceTest extends TestCase { "Found G USB Driver package, revision 43 (Obsolete)\n" +
"Found Android Vendor Extra API Dep package, revision 2 (Obsolete)\n" +
"Found Unkown Extra package, revision 2 (Obsolete)\n",
- monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedResults());
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
// check the packages we found... we expected to find 11 packages with each at least
// one archive.
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/SdkRepoSourceTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/SdkRepoSourceTest.java index aee811f..24a6fb6 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/SdkRepoSourceTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/internal/repository/SdkRepoSourceTest.java @@ -141,14 +141,15 @@ public class SdkRepoSourceTest extends TestCase { Document result = mSource._findAlternateToolsXml(xmlStream);
assertNotNull(result);
- MockMonitor mon = new MockMonitor();
- assertTrue(mSource._parsePackages(result, SdkRepoConstants.NS_URI, mon));
+ MockMonitor monitor = new MockMonitor();
+ assertTrue(mSource._parsePackages(result, SdkRepoConstants.NS_URI, monitor));
assertEquals("Found Android SDK Tools, revision 1\n" +
"Found Android SDK Tools, revision 42\n" +
"Found Android SDK Platform-tools, revision 3\n",
- mon.getCapturedDescriptions());
- assertEquals("", mon.getCapturedResults());
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
// check the packages we found... we expected to find 2 tool packages and 1
// platform-tools package, with at least 1 archive each.
@@ -199,8 +200,9 @@ public class SdkRepoSourceTest extends TestCase { "Found Android SDK Tools, revision 42\n" +
"Found This add-on has no libraries by Joe Bar, Android API 4, revision 3\n" +
"Found Usb Driver package, revision 43\n",
- monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedResults());
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
// check the packages we found... we expected to find 11 packages with each at least
// one archive.
@@ -277,8 +279,9 @@ public class SdkRepoSourceTest extends TestCase { "Found Usb Driver package, revision 43 (Obsolete)\n" +
"Found Extra API Dep package, revision 2 (Obsolete)\n" +
"Found Samples for SDK API 14, revision 24 (Obsolete)\n",
- monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedResults());
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
// check the packages we found... we expected to find 13 packages with each at least
// one archive.
@@ -353,8 +356,9 @@ public class SdkRepoSourceTest extends TestCase { "Found A USB Driver package, revision 43 (Obsolete)\n" +
"Found Android Vendor Extra API Dep package, revision 2 (Obsolete)\n" +
"Found Samples for SDK API 14, revision 24 (Obsolete)\n",
- monitor.getCapturedDescriptions());
- assertEquals("", monitor.getCapturedResults());
+ monitor.getCapturedVerboseLog());
+ assertEquals("", monitor.getCapturedLog());
+ assertEquals("", monitor.getCapturedErrorLog());
// check the packages we found... we expected to find 13 packages with each at least
// one archive.
diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/mock/MockLog.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/mock/MockLog.java index 3ef0140..3ef0140 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/mock/MockLog.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/mock/MockLog.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/SdkRepositoryTest.java index 2bbc088..2bbc088 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/repository/SdkRepositoryTest.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml index f1dce67..f1dce67 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/AndroidManifest-instrumentation.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/AndroidManifest-testapp.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/AndroidManifest-testapp.xml index f55b4e0..f55b4e0 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/AndroidManifest-testapp.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/AndroidManifest-testapp.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/AndroidManifest-testapp2.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/AndroidManifest-testapp2.xml index d5bcac7..d5bcac7 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/AndroidManifest-testapp2.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/AndroidManifest-testapp2.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/addon_sample_1.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_1.xml index d761d73..d761d73 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/addon_sample_1.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addon_sample_1.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/addons_list_sample_1.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addons_list_sample_1.xml index bda69d4..bda69d4 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/addons_list_sample_1.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/addons_list_sample_1.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_1.xml index aa209d3..aa209d3 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_1.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_2.xml index 4b8e329..4b8e329 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_2.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_3.xml b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_3.xml index 05a9c79..05a9c79 100755 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_3.xml +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/testdata/repository_sample_3.xml diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/AndroidManifestParserTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/xml/AndroidManifestParserTest.java index 02725f4..02725f4 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/AndroidManifestParserTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/xml/AndroidManifestParserTest.java diff --git a/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/SupportsScreensTest.java b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/xml/SupportsScreensTest.java index baf13b1..baf13b1 100644 --- a/sdkmanager/libs/sdklib/tests/com/android/sdklib/xml/SupportsScreensTest.java +++ b/sdkmanager/libs/sdklib/tests/src/com/android/sdklib/xml/SupportsScreensTest.java diff --git a/sdkmanager/libs/sdkuilib/.classpath b/sdkmanager/libs/sdkuilib/.classpath index b62b292..9080f57 100644 --- a/sdkmanager/libs/sdkuilib/.classpath +++ b/sdkmanager/libs/sdkuilib/.classpath @@ -1,12 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry kind="src" path="src"/> - <classpathentry kind="src" path="tests"/> + <classpathentry excluding="**/Android.mk" kind="src" path="src"/> + <classpathentry excluding="**/Android.mk" kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/> <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/> <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/> <classpathentry combineaccessrules="false" kind="src" path="/common"/> + <classpathentry kind="var" path="ANDROID_OUT_FRAMEWORK/swtmenubar.jar" sourcepath="/ANDROID_SRC/sdk/swtmenubar/src"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/sdkmanager/libs/sdkuilib/.settings/org.eclipse.jdt.core.prefs b/sdkmanager/libs/sdkuilib/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..17cc168 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,64 @@ +#Wed Mar 16 15:10:22 PDT 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/sdkmanager/libs/sdkuilib/.settings/org.eclipse.jdt.ui.prefs b/sdkmanager/libs/sdkuilib/.settings/org.eclipse.jdt.ui.prefs new file mode 100755 index 0000000..7471e0b --- /dev/null +++ b/sdkmanager/libs/sdkuilib/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,54 @@ +#Fri Apr 08 10:49:40 PDT 2011 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/sdkmanager/libs/sdkuilib/Android.mk b/sdkmanager/libs/sdkuilib/Android.mk index 8e0bc23..740615e 100644 --- a/sdkmanager/libs/sdkuilib/Android.mk +++ b/sdkmanager/libs/sdkuilib/Android.mk @@ -1,4 +1,43 @@ -# Copyright 2008 The Android Open Source Project # -SDKUILIB_LOCAL_DIR := $(call my-dir) -include $(SDKUILIB_LOCAL_DIR)/src/Android.mk +# 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. +# +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +# IMPORTANT: if you add a new dependency here, please make sure +# to also check the following file: +# sdkmanager/app/etc/android.bat +# (Note: there is no manifest.txt for sdkuilib.) +LOCAL_JAVA_LIBRARIES := \ + sdklib \ + common \ + androidprefs \ + swtmenubar \ + swt \ + org.eclipse.jface_3.4.2.M20090107-0800 \ + org.eclipse.equinox.common_3.4.0.v20080421-2006 \ + org.eclipse.core.commands_3.4.0.I20080509-2000 + +LOCAL_MODULE := sdkuilib + +include $(BUILD_HOST_JAVA_LIBRARY) + + + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/sdkmanager/libs/sdkuilib/NOTICE b/sdkmanager/libs/sdkuilib/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/sdkmanager/libs/sdkuilib/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/sdkmanager/libs/sdkuilib/README b/sdkmanager/libs/sdkuilib/README index d66b84a..dee4a24 100644 --- a/sdkmanager/libs/sdkuilib/README +++ b/sdkmanager/libs/sdkuilib/README @@ -1,11 +1,45 @@ -Using the Eclipse projects for ddmuilib. +Using the Eclipse project SdkUiLib +---------------------------------- -ddmuilib requires SWT to compile. +1- sdkuilib requires SWT to compile. -SWT is available in the depot under prebuild/<platform>/swt +SWT is available in the tree under prebuild/<platform>/swt Because the build path cannot contain relative path that are not inside the project directory, the .classpath file references a user library called ANDROID_SWT. -In order to compile the project, make a user library called ANDROID_SWT containing the jar -available at prebuild/<platform>/swt.
\ No newline at end of file +In order to compile the project: +- Open Preferences > Java > Build Path > User Libraries +- Create a new user library named ANDROID_SWT +- Add the following 4 JAR files: + + - prebuilt/<platform>/swt/swt.jar + - prebuilt/common/eclipse/org.eclipse.core.commands_3.*.jar + - prebuilt/common/eclipse/org.eclipse.equinox.common_3.*.jar + - prebuilt/common/eclipse/org.eclipse.jface_3.*.jar + + +2- sdkuilib also requires the compiled swtmenubar library. + +Build the swtmenubar library: +$ cd $TOP (top of Android tree) +$ . build/envsetup.sh && lunch sdk-eng +$ sdk/eclipse/scripts/create_sdkman_symlinks.sh + +Define a classpath variable in Eclipse: +- Open Preferences > Java > Build Path > Classpath Variables +- Create a new classpath variable named ANDROID_OUT_FRAMEWORK +- Set its folder value to <Android tree>/out/host/<platform>/framework +- Create a new classpath variable named ANDROID_SRC +- Set its folder value to <Android tree> + +You might need to clean the SdkUiLib project (Project > Clean...) after +you add the new classpath variable, otherwise previous errors might not +go away automatically. + +The ANDROID_SRC part should be optional. It allows you to have access to +the SwtMenuBar generic parts from the Java editor. + + +-- +EOF diff --git a/sdkmanager/libs/sdkuilib/src/Android.mk b/sdkmanager/libs/sdkuilib/src/Android.mk deleted file mode 100644 index 9aceba1..0000000 --- a/sdkmanager/libs/sdkuilib/src/Android.mk +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2008 The Android Open Source Project -# -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := . - -LOCAL_JAVA_LIBRARIES := \ - sdklib \ - common \ - androidprefs \ - swt \ - org.eclipse.jface_3.4.2.M20090107-0800 \ - org.eclipse.equinox.common_3.4.0.v20080421-2006 \ - org.eclipse.core.commands_3.4.0.I20080509-2000 - -LOCAL_MODULE := sdkuilib - -include $(BUILD_HOST_JAVA_LIBRARY) - diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AddonSitesDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AddonSitesDialog.java new file mode 100755 index 0000000..9502099 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AddonSitesDialog.java @@ -0,0 +1,417 @@ +/* + * 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.sdkuilib.internal.repository; + +import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.repository.SdkAddonSource; +import com.android.sdklib.internal.repository.SdkSource; +import com.android.sdklib.internal.repository.SdkSourceCategory; +import com.android.sdklib.internal.repository.SdkSources; + +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Dialog; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +import java.util.Arrays; + +public class AddonSitesDialog extends Dialog { + + /** + * Min Y location for dialog. Need to deal with the menu bar on mac os. + */ + private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? + 20 : 0; + + + /** Last dialog size for this session. */ + private static Point sLastSize; + + private final UpdaterData mUpdaterData; + private boolean mChanged; + + private Shell mShell; + private Table mTable; + private TableViewer mTableViewer; + private Button mButtonNew; + private Button mButtonDelete; + private Button mButtonClose; + private Label mlabel; + private Button mButtonEdit; + private TableColumn mColumnUrl; + + /** + * Create the dialog. + * + * @param parent The parent's shell + */ + public AddonSitesDialog(Shell parent, UpdaterData updaterData) { + super(parent, SWT.NONE); + mUpdaterData = updaterData; + setText("Add-on Sites"); + } + + /** + * Open the dialog. + * + * @return True if anything was changed. + */ + public boolean open() { + createContents(); + positionShell(); + postCreate(); + mShell.open(); + mShell.layout(); + Display display = getParent().getDisplay(); + while (!mShell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + + return mChanged; + } + + /** + * Create contents of the dialog. + */ + private void createContents() { + mShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.APPLICATION_MODAL); + mShell.setMinimumSize(new Point(450, 300)); + mShell.setSize(450, 300); + mShell.setText(getText()); + GridLayout gl_shell = new GridLayout(); + gl_shell.numColumns = 2; + mShell.setLayout(gl_shell); + + mlabel = new Label(mShell, SWT.NONE); + mlabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + mlabel.setText( + "This dialog lets you manage the URLs of external add-on sites to be used.\n" + + "\n" + + "Add-on sites can provide new add-ons or \"user\" packages.\n" + + "They cannot provide standard Android platforms, docs or samples packages.\n" + + "Adding a URL here will not allow you to clone an official Android repository." + ); + + mTableViewer = new TableViewer(mShell, SWT.BORDER | SWT.FULL_SELECTION); + mTableViewer.addPostSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + on_TableViewer_selectionChanged(event); + } + }); + mTable = mTableViewer.getTable(); + mTable.setLinesVisible(false); + mTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + on_Table_mouseUp(e); + } + }); + mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 5)); + + TableViewerColumn tableViewerColumn = new TableViewerColumn(mTableViewer, SWT.NONE); + mColumnUrl = tableViewerColumn.getColumn(); + mColumnUrl.setWidth(100); + mColumnUrl.setText("New Column"); + + mButtonNew = new Button(mShell, SWT.NONE); + mButtonNew.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + newOrEdit(false /*isEdit*/); + } + }); + mButtonNew.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mButtonNew.setText("New..."); + + mButtonEdit = new Button(mShell, SWT.NONE); + mButtonEdit.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + newOrEdit(true /*isEdit*/); + } + }); + mButtonEdit.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mButtonEdit.setText("Edit..."); + + mButtonDelete = new Button(mShell, SWT.NONE); + mButtonDelete.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + on_ButtonDelete_widgetSelected(e); + } + }); + mButtonDelete.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mButtonDelete.setText("Delete..."); + new Label(mShell, SWT.NONE); + + mButtonClose = new Button(mShell, SWT.NONE); + mButtonClose.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + on_ButtonClose_widgetSelected(e); + } + }); + mButtonClose.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1)); + mButtonClose.setText("Close"); + + adjustColumnsWidth(mTable, mColumnUrl); + } + + /** + * Adds a listener to adjust the column width when the parent is resized. + */ + private void adjustColumnsWidth(final Table table, final TableColumn column0) { + // Add a listener to resize the column to the full width of the table + table.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = table.getClientArea(); + column0.setWidth(r.width * 100 / 100); // 100% + } + }); + } + + /** + * Centers the dialog in its parent shell. + */ + private void positionShell() { + // Centers the dialog in its parent shell + Shell child = mShell; + Shell parent = getParent(); + if (child != null && parent != null) { + + // get the parent client area with a location relative to the display + Rectangle parentArea = parent.getClientArea(); + Point parentLoc = parent.getLocation(); + int px = parentLoc.x; + int py = parentLoc.y; + int pw = parentArea.width; + int ph = parentArea.height; + + // Reuse the last size if there's one, otherwise use the default + Point childSize = sLastSize != null ? sLastSize : child.getSize(); + int cw = childSize.x; + int ch = childSize.y; + + int x = px + (pw - cw) / 2; + if (x < 0) x = 0; + + int y = py + (ph - ch) / 2; + if (y < MIN_Y) y = MIN_Y; + + child.setLocation(x, y); + child.setSize(cw, ch); + } + } + + private void newOrEdit(final boolean isEdit) { + SdkSources sources = mUpdaterData.getSources(); + final SdkSource[] knownSources = sources.getAllSources(); + String title = isEdit ? "Edit Add-on Site URL" : "Add Add-on Site URL"; + String msg = "Please enter the URL of the addon.xml:"; + IStructuredSelection sel = (IStructuredSelection) mTableViewer.getSelection(); + final String initialValue = !isEdit || sel.isEmpty() ? null : + sel.getFirstElement().toString(); + + if (isEdit && initialValue == null) { + // Edit with no actual value is not supposed to happen. Ignore this case. + return; + } + + InputDialog dlg = new InputDialog(mShell, title, msg, initialValue, new IInputValidator() { + public String isValid(String newText) { + + newText = newText == null ? null : newText.trim(); + + if (newText == null || newText.length() == 0) { + return "Error: URL field is empty. Please enter a URL."; + } + + // A URL should have one of the following prefixes + if (!newText.startsWith("file://") && //$NON-NLS-1$ + !newText.startsWith("ftp://") && //$NON-NLS-1$ + !newText.startsWith("http://") && //$NON-NLS-1$ + !newText.startsWith("https://")) { //$NON-NLS-1$ + return "Error: The URL must start by one of file://, ftp://, http:// or https://"; + } + + if (isEdit && newText.equals(initialValue)) { + // Edited value hasn't changed. This isn't an error. + return null; + } + + // Reject URLs that are already in the source list. + // URLs are generally case-insensitive (except for file:// where it all depends + // on the current OS so we'll ignore this case.) + for (SdkSource s : knownSources) { + if (newText.equalsIgnoreCase(s.getUrl())) { + return "Error: This site is already listed."; + } + } + + return null; + } + }); + + if (dlg.open() == Window.OK) { + String url = dlg.getValue().trim(); + + if (!url.equals(initialValue)) { + if (isEdit && initialValue != null) { + // Remove the old value before we add the new one, which is we just + // asserted will be different. + for (SdkSource source : sources.getSources(SdkSourceCategory.USER_ADDONS)) { + if (initialValue.equals(source.getUrl())) { + sources.remove(source); + break; + } + } + + } + + // create the source, store it and update the list + SdkAddonSource newSource = new SdkAddonSource(url, null/*uiName*/); + sources.add( + SdkSourceCategory.USER_ADDONS, + newSource); + mChanged = true; + loadList(); + + // select the new source + IStructuredSelection newSel = new StructuredSelection(newSource); + mTableViewer.setSelection(newSel, true /*reveal*/); + } + } + } + + private void on_ButtonDelete_widgetSelected(SelectionEvent e) { + IStructuredSelection sel = (IStructuredSelection) mTableViewer.getSelection(); + String selectedUrl = sel.isEmpty() ? null : sel.getFirstElement().toString(); + + if (selectedUrl == null) { + return; + } + + MessageBox mb = new MessageBox(mShell, + SWT.YES | SWT.NO | SWT.ICON_QUESTION | SWT.APPLICATION_MODAL); + mb.setText("Delete add-on site"); + mb.setMessage(String.format("Do you want to delete the URL %1$s?", selectedUrl)); + if (mb.open() == SWT.YES) { + SdkSources sources = mUpdaterData.getSources(); + for (SdkSource source : sources.getSources(SdkSourceCategory.USER_ADDONS)) { + if (selectedUrl.equals(source.getUrl())) { + sources.remove(source); + mChanged = true; + loadList(); + } + } + } + } + + private void on_ButtonClose_widgetSelected(SelectionEvent e) { + mShell.close(); + } + + private void on_Table_mouseUp(MouseEvent e) { + Point p = new Point(e.x, e.y); + if (mTable.getItem(p) == null) { + mTable.deselectAll(); + on_TableViewer_selectionChanged(null /*event*/); + } + } + + private void on_TableViewer_selectionChanged(SelectionChangedEvent event) { + ISelection sel = mTableViewer.getSelection(); + mButtonDelete.setEnabled(!sel.isEmpty()); + mButtonEdit.setEnabled(!sel.isEmpty()); + } + + private void postCreate() { + // initialize the list + mTableViewer.setLabelProvider(new LabelProvider()); + mTableViewer.setContentProvider(new SourcesContentProvider()); + loadList(); + } + + private void loadList() { + if (mUpdaterData != null) { + SdkSource[] knownSources = + mUpdaterData.getSources().getSources(SdkSourceCategory.USER_ADDONS); + Arrays.sort(knownSources); + + ISelection oldSelection = mTableViewer.getSelection(); + + mTableViewer.setInput(knownSources); + mTableViewer.refresh(); + // initialize buttons' state that depend on the list + on_TableViewer_selectionChanged(null /*event*/); + + if (oldSelection != null && !oldSelection.isEmpty()) { + mTableViewer.setSelection(oldSelection, true /*reveal*/); + } + } + } + + private static class SourcesContentProvider implements IStructuredContentProvider { + + public void dispose() { + // pass + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // pass + } + + public Object[] getElements(Object inputElement) { + if (inputElement instanceof SdkSource[]) { + return (Object[]) inputElement; + } else { + return new Object[0]; + } + } + } +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java index 7594f16..7c78983 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/AvdManagerPage.java @@ -17,7 +17,6 @@ package com.android.sdkuilib.internal.repository;
import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdkuilib.internal.widgets.AvdSelector;
import com.android.sdkuilib.internal.widgets.AvdSelector.DisplayMode;
import com.android.sdkuilib.repository.ISdkChangeListener;
@@ -58,7 +57,7 @@ public class AvdManagerPage extends Composite implements ISdkChangeListener { try {
label.setText(String.format(
"List of existing Android Virtual Devices located at %s",
- AvdManager.getBaseAvdFolder()));
+ mUpdaterData.getAvdManager().getBaseAvdFolder()));
} catch (AndroidLocationException e) {
label.setText(e.getMessage());
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/IPageListener.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/IPageListener.java new file mode 100755 index 0000000..fc59404 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/IPageListener.java @@ -0,0 +1,30 @@ +/* + * 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.sdkuilib.internal.repository; + + + +/** + * Interface for lifecycle events of pages. + */ +interface IPageListener { + + /** + * The page was just selected and brought to front. + */ + public void onPageSelected(); +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java index d579094..d2824ea 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/LocalPackagesPage.java @@ -42,6 +42,9 @@ import org.eclipse.swt.widgets.TableColumn; import java.io.File;
+/**
+ * Page that displays all locally installed packages from the current SDK.
+ */
public class LocalPackagesPage extends Composite implements ISdkChangeListener {
private final UpdaterData mUpdaterData;
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java new file mode 100755 index 0000000..708c2d6 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/PackagesPage.java @@ -0,0 +1,1441 @@ +/* + * 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.sdkuilib.internal.repository; + +import com.android.sdklib.internal.repository.Archive; +import com.android.sdklib.internal.repository.IDescription; +import com.android.sdklib.internal.repository.IPackageVersion; +import com.android.sdklib.internal.repository.ITask; +import com.android.sdklib.internal.repository.ITaskMonitor; +import com.android.sdklib.internal.repository.Package; +import com.android.sdklib.internal.repository.PlatformPackage; +import com.android.sdklib.internal.repository.PlatformToolPackage; +import com.android.sdklib.internal.repository.SdkSource; +import com.android.sdklib.internal.repository.ToolPackage; +import com.android.sdklib.internal.repository.Package.UpdateInfo; +import com.android.sdkuilib.internal.repository.icons.ImageFactory; +import com.android.sdkuilib.repository.ISdkChangeListener; +import com.android.util.Pair; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.ITableColorProvider; +import org.eclipse.jface.viewers.ITableFontProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeColumnViewerLabelProvider; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +/** + * Page that displays both locally installed packages as well as all known + * remote available packages. This gives an overview of what is installed + * vs what is available and allows the user to update or install packages. + */ +public class PackagesPage extends Composite + implements ISdkChangeListener, IPageListener { + + private static final String ICON_CAT_OTHER = "pkgcat_other_16.png"; //$NON-NLS-1$ + private static final String ICON_CAT_PLATFORM = "pkgcat_16.png"; //$NON-NLS-1$ + private static final String ICON_SORT_BY_SOURCE = "source_icon16.png"; //$NON-NLS-1$ + private static final String ICON_SORT_BY_API = "platform_pkg_16.png"; //$NON-NLS-1$ + private static final String ICON_PKG_NEW = "pkg_new_16.png"; //$NON-NLS-1$ + private static final String ICON_PKG_UPDATE = "pkg_update_16.png"; //$NON-NLS-1$ + private static final String ICON_PKG_INSTALLED = "pkg_installed_16.png"; //$NON-NLS-1$ + + enum MenuAction { + RELOAD (SWT.NONE, "Reload"), + SHOW_ADDON_SITES (SWT.NONE, "Manage Sources..."), + TOGGLE_SHOW_ARCHIVES (SWT.CHECK, "Show Archives"), + TOGGLE_SHOW_INSTALLED_PKG (SWT.CHECK, "Show Installed Packages"), + TOGGLE_SHOW_OBSOLETE_PKG (SWT.CHECK, "Show Obsolete Packages"), + TOGGLE_SHOW_UPDATE_NEW_PKG (SWT.CHECK, "Show Updates/New Packages"), + SORT_API_LEVEL (SWT.RADIO, "Sort by API Level"), + SORT_SOURCE (SWT.RADIO, "Sort by Source") + ; + + private final int mMenuStyle; + private final String mMenuTitle; + + MenuAction(int menuStyle, String menuTitle) { + mMenuStyle = menuStyle; + mMenuTitle = menuTitle; + } + + public int getMenuStyle() { + return mMenuStyle; + } + + public String getMenuTitle() { + return mMenuTitle; + } + }; + + private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>(); + + private final List<PkgItem> mPackages = new ArrayList<PkgItem>(); + private final List<PkgCategory> mCategories = new ArrayList<PkgCategory>(); + private final UpdaterData mUpdaterData; + + private boolean mDisplayArchives = false; + + private Text mTextSdkOsPath; + private Button mCheckSortSource; + private Button mCheckSortApi; + private Button mCheckFilterObsolete; + private Button mCheckFilterInstalled; + private Button mCheckFilterNew; + private Composite mGroupOptions; + private Composite mGroupSdk; + private Group mGroupPackages; + private Button mButtonDelete; + private Button mButtonInstall; + private Tree mTree; + private CheckboxTreeViewer mTreeViewer; + private TreeViewerColumn mColumnName; + private TreeViewerColumn mColumnApi; + private TreeViewerColumn mColumnRevision; + private TreeViewerColumn mColumnStatus; + private Color mColorUpdate; + private Font mTreeFontItalic; + private TreeColumn mTreeColumnName; + + public PackagesPage(Composite parent, UpdaterData updaterData) { + super(parent, SWT.NONE); + + mUpdaterData = updaterData; + createContents(this); + + postCreate(); //$hide$ + } + + public void onPageSelected() { + if (mPackages.isEmpty()) { + // Initialize the package list the first time the page is shown. + loadPackages(); + } + } + + private void createContents(Composite parent) { + GridLayout gridLayout = new GridLayout(2, false); + gridLayout.marginWidth = 0; + gridLayout.marginHeight = 0; + parent.setLayout(gridLayout); + + mGroupSdk = new Composite(parent, SWT.NONE); + mGroupSdk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); + mGroupSdk.setLayout(new GridLayout(2, false)); + + Label label1 = new Label(mGroupSdk, SWT.NONE); + label1.setText("SDK Path:"); + + mTextSdkOsPath = new Text(mGroupSdk, SWT.NONE); + mTextSdkOsPath.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mTextSdkOsPath.setEnabled(false); + + mGroupPackages = new Group(parent, SWT.NONE); + mGroupPackages.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + mGroupPackages.setText("Packages"); + mGroupPackages.setLayout(new GridLayout(1, false)); + + mTreeViewer = new CheckboxTreeViewer(mGroupPackages, SWT.BORDER); + + mTreeViewer.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent event) { + onTreeCheckStateChanged(event); //$hide$ + } + }); + + mTree = mTreeViewer.getTree(); + mTree.setLinesVisible(true); + mTree.setHeaderVisible(true); + mTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + + // column name icon is set in sortPackages() depending on the current filter type + // (e.g. API level or source) + mColumnName = new TreeViewerColumn(mTreeViewer, SWT.NONE); + mTreeColumnName = mColumnName.getColumn(); + mTreeColumnName.setWidth(340); + mTreeColumnName.setText("Name"); + + mColumnApi = new TreeViewerColumn(mTreeViewer, SWT.NONE); + TreeColumn treeColumn2 = mColumnApi.getColumn(); + treeColumn2.setAlignment(SWT.CENTER); + treeColumn2.setWidth(50); + treeColumn2.setText("API"); + + mColumnRevision = new TreeViewerColumn(mTreeViewer, SWT.NONE); + TreeColumn treeColumn3 = mColumnRevision.getColumn(); + treeColumn3.setAlignment(SWT.CENTER); + treeColumn3.setWidth(50); + treeColumn3.setText("Rev."); + treeColumn3.setToolTipText("Revision currently installed"); + + + mColumnStatus = new TreeViewerColumn(mTreeViewer, SWT.NONE); + TreeColumn treeColumn4 = mColumnStatus.getColumn(); + treeColumn4.setAlignment(SWT.LEAD); + treeColumn4.setWidth(190); + treeColumn4.setText("Status"); + + mGroupOptions = new Composite(mGroupPackages, SWT.NONE); + mGroupOptions.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + GridLayout gl_GroupOptions = new GridLayout(6, false); + gl_GroupOptions.marginWidth = 0; + gl_GroupOptions.marginHeight = 0; + mGroupOptions.setLayout(gl_GroupOptions); + + Label label3 = new Label(mGroupOptions, SWT.NONE); + label3.setText("Show:"); + + mCheckFilterNew = new Button(mGroupOptions, SWT.CHECK); + mCheckFilterNew.setToolTipText("Show Updates and New"); + mCheckFilterNew.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + sortPackages(true /*updateButtons*/); + } + }); + mCheckFilterNew.setSelection(true); + mCheckFilterNew.setText("Updates/New"); + + mCheckFilterInstalled = new Button(mGroupOptions, SWT.CHECK); + mCheckFilterInstalled.setToolTipText("Show Installed"); + mCheckFilterInstalled.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + sortPackages(true /*updateButtons*/); + } + }); + mCheckFilterInstalled.setSelection(true); + mCheckFilterInstalled.setText("Installed"); + + mCheckFilterObsolete = new Button(mGroupOptions, SWT.CHECK); + mCheckFilterObsolete.setToolTipText("Also show obsolete packages"); + mCheckFilterObsolete.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + sortPackages(true /*updateButtons*/); + } + }); + mCheckFilterObsolete.setSelection(false); + mCheckFilterObsolete.setText("Obsolete"); + + Label placeholder2 = new Label(mGroupOptions, SWT.NONE); + placeholder2.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + + mButtonInstall = new Button(mGroupOptions, SWT.NONE); + mButtonInstall.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mButtonInstall.setToolTipText("Install all the selected packages"); + mButtonInstall.setText("Install Selected..."); + mButtonInstall.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onButtonInstall(); //$hide$ + } + }); + + Label label2 = new Label(mGroupOptions, SWT.NONE); + label2.setText("Sort by:"); + + mCheckSortApi = new Button(mGroupOptions, SWT.RADIO); + mCheckSortApi.setToolTipText("Sort by API level"); + mCheckSortApi.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + sortPackages(true /*updateButtons*/); + // Reset the expanded state when changing sort algorithm + expandInitial(mCategories); + } + }); + mCheckSortApi.setText("API level"); + mCheckSortApi.setSelection(true); + + mCheckSortSource = new Button(mGroupOptions, SWT.RADIO); + mCheckSortSource.setToolTipText("Sort by Source"); + mCheckSortSource.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + sortPackages(true /*updateButtons*/); + // Reset the expanded state when changing sort algorithm + expandInitial(mCategories); + } + }); + mCheckSortSource.setText("Source"); + + new Label(mGroupOptions, SWT.NONE); + new Label(mGroupOptions, SWT.NONE); + + mButtonDelete = new Button(mGroupOptions, SWT.NONE); + mButtonDelete.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mButtonDelete.setToolTipText("Delete an installed package"); + mButtonDelete.setText("Delete..."); + mButtonDelete.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onButtonDelete(); //$hide$ + } + }); + } + + private Image getImage(String filename) { + if (mUpdaterData != null) { + ImageFactory imgFactory = mUpdaterData.getImageFactory(); + if (imgFactory != null) { + return imgFactory.getImageByName(filename); + } + } + return null; + } + + + // -- Start of internal part ---------- + // Hide everything down-below from SWT designer + //$hide>>$ + + + // --- menu interactions --- + + public void registerMenuAction(final MenuAction action, MenuItem item) { + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + Button button = null; + + switch (action) { + case RELOAD: + loadPackages(); + break; + case SHOW_ADDON_SITES: + AddonSitesDialog d = new AddonSitesDialog(getShell(), mUpdaterData); + if (d.open()) { + loadPackages(); + } + break; + case TOGGLE_SHOW_ARCHIVES: + mDisplayArchives = !mDisplayArchives; + sortPackages(true /*updateButtons*/); + break; + case TOGGLE_SHOW_INSTALLED_PKG: + button = mCheckFilterInstalled; + break; + case TOGGLE_SHOW_OBSOLETE_PKG: + button = mCheckFilterObsolete; + break; + case TOGGLE_SHOW_UPDATE_NEW_PKG: + button = mCheckFilterNew; + break; + case SORT_API_LEVEL: + button = mCheckSortApi; + break; + case SORT_SOURCE: + button = mCheckSortSource; + break; + } + + if (button != null && !button.isDisposed()) { + // Toggle this button (radio or checkbox) + + boolean value = button.getSelection(); + + // SWT doesn't automatically switch radio buttons when using the + // Widget#setSelection method, so we'll do it here manually. + if (!value && (button.getStyle() & SWT.RADIO) != 0) { + // we'll be selecting this radio button, so deselect all ther other ones + // in the parent group. + for (Control child : button.getParent().getChildren()) { + if (child instanceof Button && + child != button && + (child.getStyle() & SWT.RADIO) != 0) { + ((Button) child).setSelection(value); + } + } + } + + button.setSelection(!value); + + // SWT doesn't actually invoke the listeners when using Widget#setSelection + // so let's run the actual action. + button.notifyListeners(SWT.Selection, new Event()); + } + + updateMenuCheckmarks(); + } + }); + + mMenuActions.put(action, item); + } + + // --- internal methods --- + + private void updateMenuCheckmarks() { + + for (Entry<MenuAction, MenuItem> entry : mMenuActions.entrySet()) { + MenuAction action = entry.getKey(); + MenuItem item = entry.getValue(); + + if (action.getMenuStyle() == SWT.NONE) { + continue; + } + + boolean value = false; + Button button = null; + + switch (action) { + case TOGGLE_SHOW_ARCHIVES: + value = mDisplayArchives; + break; + case TOGGLE_SHOW_INSTALLED_PKG: + button = mCheckFilterInstalled; + break; + case TOGGLE_SHOW_OBSOLETE_PKG: + button = mCheckFilterObsolete; + break; + case TOGGLE_SHOW_UPDATE_NEW_PKG: + button = mCheckFilterNew; + break; + case SORT_API_LEVEL: + button = mCheckSortApi; + break; + case SORT_SOURCE: + button = mCheckSortSource; + break; + } + + if (button != null && !button.isDisposed()) { + value = button.getSelection(); + } + + item.setSelection(value); + } + + } + + private void postCreate() { + if (mUpdaterData != null) { + mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot()); + } + + mTreeViewer.setContentProvider(new PkgContentProvider()); + + mColumnApi.setLabelProvider( + new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnApi))); + mColumnName.setLabelProvider( + new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnName))); + mColumnStatus.setLabelProvider( + new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnStatus))); + mColumnRevision.setLabelProvider( + new TreeColumnViewerLabelProvider(new PkgCellLabelProvider(mColumnRevision))); + + FontData fontData = mTree.getFont().getFontData()[0]; + fontData.setStyle(SWT.ITALIC); + mTreeFontItalic = new Font(mTree.getDisplay(), fontData); + + mColorUpdate = new Color(mTree.getDisplay(), 0xff, 0xff, 0xcc); + + mTree.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + mTreeFontItalic.dispose(); + mColorUpdate.dispose(); + mTreeFontItalic = null; + mColorUpdate = null; + } + }); + } + + private void loadPackages() { + if (mUpdaterData == null) { + return; + } + + try { + enableUi(mGroupPackages, false); + + boolean firstLoad = mPackages.size() == 0; + mPackages.clear(); + + // get local packages + for (Package pkg : mUpdaterData.getInstalledPackages()) { + PkgItem pi = new PkgItem(pkg, PkgState.INSTALLED); + mPackages.add(pi); + } + + // get remote packages + final boolean forceHttp = mUpdaterData.getSettingsController().getForceHttp(); + mUpdaterData.loadRemoteAddonsList(); + mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() { + public void run(ITaskMonitor monitor) { + SdkSource[] sources = mUpdaterData.getSources().getAllSources(); + for (SdkSource source : sources) { + Package[] pkgs = source.getPackages(); + if (pkgs == null) { + source.load(monitor, forceHttp); + pkgs = source.getPackages(); + } + if (pkgs == null) { + continue; + } + + nextPkg: for(Package pkg : pkgs) { + boolean isUpdate = false; + for (PkgItem pi: mPackages) { + if (pi.isSameAs(pkg)) { + continue nextPkg; + } + if (pi.isUpdatedBy(pkg)) { + isUpdate = true; + break; + } + } + + if (!isUpdate) { + PkgItem pi = new PkgItem(pkg, PkgState.NEW); + mPackages.add(pi); + } + } + + // Dynamically update the table while we load after each source. + // Since the official Android source gets loaded first, it makes the + // window look non-empty a lot sooner. + mGroupPackages.getDisplay().syncExec(new Runnable() { + public void run() { + sortPackages(true /*updateButtons*/); + } + }); + } + + monitor.setDescription("Done loading %1$d packages from %2$d sources", + mPackages.size(), + sources.length); + } + }); + + if (firstLoad) { + // set the initial expanded state + expandInitial(mCategories); + } + + } finally { + enableUi(mGroupPackages, true); + updateButtonsState(); + updateMenuCheckmarks(); + } + } + + private void enableUi(Composite root, boolean enabled) { + root.setEnabled(enabled); + for (Control child : root.getChildren()) { + if (child instanceof Composite) { + enableUi((Composite) child, enabled); + } else { + child.setEnabled(enabled); + } + } + } + + private void sortPackages(boolean updateButtons) { + if (mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection()) { + sortByApiLevel(); + } else { + sortBySource(); + } + if (updateButtons) { + updateButtonsState(); + updateMenuCheckmarks(); + } + } + + /** + * Recompute the tree by sorting all the packages by API. + * This does an update in-place of the mCategories list so that the table + * can preserve its state (checked / expanded / selected) properly. + */ + private void sortByApiLevel() { + + ImageFactory imgFactory = mUpdaterData.getImageFactory(); + + if (!mTreeColumnName.isDisposed()) { + mTreeColumnName.setImage(getImage(ICON_SORT_BY_API)); + } + + // keep a map of the initial state so that we can detect which items or categories are + // no longer being used, so that we can removed them at the end of the in-place update. + final Map<Integer, Pair<PkgCategory, HashSet<PkgItem>> > unusedItemsMap = + new HashMap<Integer, Pair<PkgCategory, HashSet<PkgItem>> >(); + final Set<PkgCategory> unusedCatSet = new HashSet<PkgCategory>(); + + // get existing categories + for (PkgCategory cat : mCategories) { + unusedCatSet.add(cat); + unusedItemsMap.put(cat.getKey(), Pair.of(cat, new HashSet<PkgItem>(cat.getItems()))); + } + + // always add the tools & extras categories, even if empty (unlikely anyway) + if (!unusedItemsMap.containsKey(PkgCategory.KEY_TOOLS)) { + PkgCategory cat = new PkgCategory( + PkgCategory.KEY_TOOLS, + "Tools", + imgFactory.getImageByName(ICON_CAT_OTHER)); + unusedItemsMap.put(PkgCategory.KEY_TOOLS, Pair.of(cat, new HashSet<PkgItem>())); + mCategories.add(cat); + } + + if (!unusedItemsMap.containsKey(PkgCategory.KEY_EXTRA)) { + PkgCategory cat = new PkgCategory( + PkgCategory.KEY_EXTRA, + "Add-ons & Extras", + imgFactory.getImageByName(ICON_CAT_OTHER)); + unusedItemsMap.put(PkgCategory.KEY_EXTRA, Pair.of(cat, new HashSet<PkgItem>())); + mCategories.add(cat); + } + + for (PkgItem item : mPackages) { + if (!keepItem(item)) { + continue; + } + + int apiKey = item.getApi(); + + if (apiKey < 1) { + Package p = item.getPackage(); + if (p instanceof ToolPackage || p instanceof PlatformToolPackage) { + apiKey = PkgCategory.KEY_TOOLS; + } else { + apiKey = PkgCategory.KEY_EXTRA; + } + } + + Pair<PkgCategory, HashSet<PkgItem>> mapEntry = unusedItemsMap.get(apiKey); + + if (mapEntry == null) { + // This is a new category. Create it and add it to the map. + // We need a label for the category. Use null right now and set it later. + PkgCategory cat = new PkgCategory( + apiKey, + null /*label*/, + imgFactory.getImageByName(ICON_CAT_PLATFORM)); + mapEntry = Pair.of(cat, new HashSet<PkgItem>()); + unusedItemsMap.put(apiKey, mapEntry); + mCategories.add(0, cat); + } + PkgCategory cat = mapEntry.getFirst(); + assert cat != null; + unusedCatSet.remove(cat); + + HashSet<PkgItem> unusedItemsSet = mapEntry.getSecond(); + unusedItemsSet.remove(item); + if (!cat.getItems().contains(item)) { + cat.getItems().add(item); + } + + if (apiKey != -1 && cat.getLabel() == null) { + // Check whether we can get the actual platform version name (e.g. "1.5") + // from the first Platform package we find in this category. + Package p = item.getPackage(); + if (p instanceof PlatformPackage) { + String vn = ((PlatformPackage) p).getVersionName(); + String name = String.format("Android %1$s (API %2$d)", vn, apiKey); + cat.setLabel(name); + } + } + } + + for (Iterator<PkgCategory> iterCat = mCategories.iterator(); iterCat.hasNext(); ) { + PkgCategory cat = iterCat.next(); + + // Remove any unused categories. + if (unusedCatSet.contains(cat)) { + iterCat.remove(); + continue; + } + + // Remove any unused items in the category. + Integer apikey = cat.getKey(); + Pair<PkgCategory, HashSet<PkgItem>> mapEntry = unusedItemsMap.get(apikey); + assert mapEntry != null; + HashSet<PkgItem> unusedItems = mapEntry.getSecond(); + for (Iterator<PkgItem> iterItem = cat.getItems().iterator(); iterItem.hasNext(); ) { + PkgItem item = iterItem.next(); + if (unusedItems.contains(item)) { + iterItem.remove(); + } + } + + // Sort the items + Collections.sort(cat.getItems()); + + // Fix the category name for any API where we might not have found a platform package. + if (cat.getLabel() == null) { + int api = cat.getKey().intValue(); + String name = String.format("API %1$d", api); + cat.setLabel(name); + } + } + + // Sort the categories list in decreasing order + Collections.sort(mCategories, new Comparator<PkgCategory>() { + public int compare(PkgCategory cat1, PkgCategory cat2) { + // compare in descending order (o2-o1) + return cat2.getKey().compareTo(cat1.getKey()); + } + }); + + if (mTreeViewer.getInput() != mCategories) { + // set initial input + mTreeViewer.setInput(mCategories); + } else { + // refresh existing, which preserves the expanded state, the selection + // and the checked state. + mTreeViewer.refresh(); + } + } + + /** + * Recompute the tree by sorting all packages by source. + */ + private void sortBySource() { + + if (!mTreeColumnName.isDisposed()) { + mTreeColumnName.setImage(getImage(ICON_SORT_BY_SOURCE)); + } + + mCategories.clear(); + + Set<SdkSource> sourceSet = new HashSet<SdkSource>(); + for (PkgItem item : mPackages) { + if (keepItem(item)) { + sourceSet.add(item.getSource()); + } + } + + SdkSource[] sources = sourceSet.toArray(new SdkSource[sourceSet.size()]); + Arrays.sort(sources, new Comparator<SdkSource>() { + public int compare(SdkSource o1, SdkSource o2) { + if (o1 == o2) { + return 0; + } else if (o1 == null && o2 != null) { + return -1; + } else if (o1 != null && o2 == null) { + return 1; + } + assert o1 != null; + return o1.toString().compareTo(o2.toString()); + } + }); + + for (SdkSource source : sources) { + Object key = source != null ? source : "Installed Packages"; + Object iconRef = source != null ? source : + mUpdaterData.getImageFactory().getImageByName(ICON_PKG_INSTALLED); + + PkgCategory cat = new PkgCategory( + key.hashCode(), + key.toString(), + iconRef); + + for (PkgItem item : mPackages) { + if (item.getSource() == source) { + cat.getItems().add(item); + } + } + + mCategories.add(cat); + } + + // We don't support in-place incremental updates so the table gets reset + // each time we load when sorted by source. + mTreeViewer.setInput(mCategories); + } + + /** + * Decide whether to keep an item in the current tree based on user-choosen filter options. + */ + private boolean keepItem(PkgItem item) { + if (!mCheckFilterObsolete.getSelection()) { + if (item.isObsolete()) { + return false; + } + } + + if (!mCheckFilterInstalled.getSelection()) { + if (item.getState() == PkgState.INSTALLED) { + return false; + } + } + + if (!mCheckFilterNew.getSelection()) { + if (item.getState() == PkgState.NEW || + item.getState() == PkgState.HAS_UPDATE) { + return false; + } + } + + return true; + } + + /** + * Performs the initial expansion of the tree. The expands categories that contains + * at least one installed item and collapses the ones with nothing installed. + */ + private void expandInitial(Object elem) { + mTreeViewer.setExpandedState(elem, true); + for (Object pkg : + ((ITreeContentProvider) mTreeViewer.getContentProvider()).getChildren(elem)) { + if (pkg instanceof PkgCategory) { + PkgCategory cat = (PkgCategory) pkg; + for (PkgItem item : cat.getItems()) { + if (item.getState() == PkgState.INSTALLED + || item.getState() == PkgState.HAS_UPDATE) { + expandInitial(pkg); + break; + } + } + } + } + } + + /** + * Handle checking and unchecking of the tree items. + * + * When unchecking, all sub-tree items checkboxes are cleared too. + * When checking a source, all of its packages are checked too. + * When checking a package, only its compatible archives are checked. + */ + private void onTreeCheckStateChanged(CheckStateChangedEvent event) { + boolean b = event.getChecked(); + Object elem = event.getElement(); + + assert event.getSource() == mTreeViewer; + + // when deselecting, we just deselect all children too + if (b == false) { + mTreeViewer.setSubtreeChecked(elem, b); + updateButtonsState(); + return; + } + + ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider(); + + // When selecting, we want to only select compatible archives and expand the super nodes. + checkExpandItem(elem, provider); + + updateButtonsState(); + } + + private void checkExpandItem(Object elem, ITreeContentProvider provider) { + if (elem instanceof PkgCategory || elem instanceof PkgItem) { + mTreeViewer.setExpandedState(elem, true); + for (Object pkg : provider.getChildren(elem)) { + mTreeViewer.setChecked(pkg, true); + checkExpandItem(pkg, provider); + } + } else if (elem instanceof Package) { + selectCompatibleArchives(elem, provider); + } + } + + private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) { + for (Object archive : provider.getChildren(pkg)) { + if (archive instanceof Archive) { + mTreeViewer.setChecked(archive, ((Archive) archive).isCompatible()); + } + } + } + + private void updateButtonsState() { + boolean canInstall = false; + + if (mDisplayArchives) { + // In detail mode, we display archives so we can install if at + // least one archive is selected. + + Object[] checked = mTreeViewer.getCheckedElements(); + if (checked != null) { + for (Object c : checked) { + if (c instanceof Archive) { + if (((Archive) c).isCompatible()) { + canInstall = true; + break; + } + } + } + } + } else { + // In non-detail mode, we need to check if there are any packages + // or pkgitems selected with at least one compatible archive to be + // installed. + + Object[] checked = mTreeViewer.getCheckedElements(); + if (checked != null) { + for (Object c : checked) { + if (c instanceof Package) { + // This is an update package + if (((Package) c).hasCompatibleArchive()) { + canInstall = true; + break; + } + } else if (c instanceof PkgItem) { + if (((PkgItem) c).getPackage().hasCompatibleArchive()) { + canInstall = true; + break; + } + } + } + } + } + + mButtonInstall.setEnabled(canInstall); + + // We can only delete local archives + boolean canDelete = false; + Object[] checked = mTreeViewer.getCheckedElements(); + if (checked != null) { + for (Object c : checked) { + if (c instanceof PkgItem) { + if (((PkgItem) c).getState() == PkgState.INSTALLED) { + canDelete = true; + break; + } + } + } + } + + mButtonDelete.setEnabled(canDelete); + } + + private void onButtonInstall() { + ArrayList<Archive> archives = new ArrayList<Archive>(); + + if (mDisplayArchives) { + // In detail mode, we display archives so we can install only the + // archives that are actually selected. + + Object[] checked = mTreeViewer.getCheckedElements(); + if (checked != null) { + for (Object c : checked) { + if (c instanceof Archive) { + if (((Archive) c).isCompatible()) { + archives.add((Archive) c); + } + } + } + } + } else { + // In non-detail mode, we install all the compatible archives + // found in the selected pkg items or update packages. + + Object[] checked = mTreeViewer.getCheckedElements(); + if (checked != null) { + for (Object c : checked) { + Package p = null; + if (c instanceof Package) { + // This is an update package + p = (Package) c; + } else if (c instanceof PkgItem) { + p = ((PkgItem) c).getPackage(); + } + if (p != null) { + for (Archive a : p.getArchives()) { + if (a.isCompatible()) { + archives.add(a); + } + } + } + } + } + } + + if (mUpdaterData != null) { + try { + enableUi(mGroupPackages, false); + + mUpdaterData.updateOrInstallAll_WithGUI( + archives, + mCheckFilterObsolete.getSelection() /* includeObsoletes */); + } finally { + // loadPackages will also re-enable the UI + loadPackages(); + } + } + } + + private void onButtonDelete() { + // Find selected local packages to be delete + Object[] checked = mTreeViewer.getCheckedElements(); + if (checked == null) { + // This should not happen since the button should be disabled + return; + } + + String title = "Delete SDK Package"; + String msg = "Are you sure you want to delete:"; + final List<Archive> archives = new ArrayList<Archive>(); + + for (Object c : checked) { + if (c instanceof PkgItem && ((PkgItem) c).getState() == PkgState.INSTALLED) { + Package p = ((PkgItem) c).getPackage(); + + Archive[] as = p.getArchives(); + if (as.length == 1 && as[0] != null && as[0].isLocal()) { + Archive archive = as[0]; + String osPath = archive.getLocalOsPath(); + + File dir = new File(osPath); + if (dir.isDirectory()) { + msg += "\n - " + p.getShortDescription(); + archives.add(archive); + } + } + } + } + + if (!archives.isEmpty()) { + msg += "\n" + "This cannot be undone."; + if (MessageDialog.openQuestion(getShell(), title, msg)) { + try { + enableUi(mGroupPackages, false); + + mUpdaterData.getTaskFactory().start("Loading Sources", new ITask() { + public void run(ITaskMonitor monitor) { + monitor.setProgressMax(archives.size() + 1); + for (Archive a : archives) { + monitor.setDescription("Deleting '%1$s' (%2$s)", + a.getParentPackage().getShortDescription(), + a.getLocalOsPath()); + a.deleteLocal(); + monitor.incProgress(1); + if (monitor.isCancelRequested()) { + break; + } + } + + monitor.incProgress(1); + monitor.setDescription("Done"); + } + }); + } finally { + // loadPackages will also re-enable the UI + loadPackages(); + } + } + } + } + + // ---------------------- + + public class PkgCellLabelProvider extends ColumnLabelProvider + implements ITableFontProvider, ITableColorProvider { + + private final TreeViewerColumn mColumn; + + public PkgCellLabelProvider(TreeViewerColumn column) { + super(); + mColumn = column; + } + + @Override + public String getText(Object element) { + + if (mColumn == mColumnName) { + + if (element instanceof PkgCategory) { + return ((PkgCategory) element).getLabel(); + } else if (element instanceof PkgItem) { + return ((PkgItem) element).getName(); + } else if (element instanceof IDescription) { + return ((IDescription) element).getShortDescription(); + } + + } else if (mColumn == mColumnApi) { + + int api = -1; + if (element instanceof PkgItem) { + api = ((PkgItem) element).getApi(); + } + if (api >= 1) { + return Integer.toString(api); + } + + } else if (mColumn == mColumnRevision) { + + if (element instanceof PkgItem) { + PkgItem pkg = (PkgItem) element; + + if (pkg.getState() == PkgState.INSTALLED || + pkg.getState() == PkgState.HAS_UPDATE) { + return Integer.toString(pkg.getRevision()); + } + } + + } else if (mColumn == mColumnStatus) { + + if (element instanceof PkgItem) { + PkgItem pkg = (PkgItem) element; + + switch(pkg.getState()) { + case INSTALLED: + return "Installed"; + case HAS_UPDATE: + return "Update available"; + case NEW: + return "Not installed. New revision " + Integer.toString(pkg.getRevision()); + } + return pkg.getState().toString(); + + } else if (element instanceof Package) { + // This is an update package. + return "New revision " + Integer.toString(((Package) element).getRevision()); + } + } + + return ""; //$NON-NLS-1$ + } + + @Override + public Image getImage(Object element) { + ImageFactory imgFactory = mUpdaterData.getImageFactory(); + + if (imgFactory != null) { + if (mColumn == mColumnName) { + if (element instanceof PkgCategory) { + return imgFactory.getImageForObject(((PkgCategory) element).getIconRef()); + } else if (element instanceof PkgItem) { + return imgFactory.getImageForObject(((PkgItem) element).getPackage()); + } + return imgFactory.getImageForObject(element); + + } else if (mColumn == mColumnStatus && element instanceof PkgItem) { + switch(((PkgItem) element).getState()) { + case INSTALLED: + return imgFactory.getImageByName(ICON_PKG_INSTALLED); + case HAS_UPDATE: + return imgFactory.getImageByName(ICON_PKG_UPDATE); + case NEW: + return imgFactory.getImageByName(ICON_PKG_NEW); + } + } + } + return super.getImage(element); + } + + // -- ITableFontProvider + + public Font getFont(Object element, int columnIndex) { + if (element instanceof PkgItem) { + if (((PkgItem) element).getState() == PkgState.NEW) { + return mTreeFontItalic; + } + } else if (element instanceof Package) { + // update package + return mTreeFontItalic; + } + return super.getFont(element); + } + + // -- ITableColorProvider + + public Color getBackground(Object element, int columnIndex) { + if (element instanceof PkgItem) { + if (((PkgItem) element).getState() == PkgState.HAS_UPDATE) { + return mColorUpdate; + } + } + return null; + } + + public Color getForeground(Object element, int columnIndex) { + // Not used + return null; + } + } + + private class PkgContentProvider implements ITreeContentProvider { + + public Object[] getChildren(Object parentElement) { + + if (parentElement instanceof ArrayList<?>) { + return ((ArrayList<?>) parentElement).toArray(); + + } else if (parentElement instanceof PkgCategory) { + return ((PkgCategory) parentElement).getItems().toArray(); + + } else if (parentElement instanceof PkgItem) { + List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs(); + if (pkgs != null) { + return pkgs.toArray(); + } + + if (mDisplayArchives) { + return ((PkgItem) parentElement).getArchives(); + } + + } else if (parentElement instanceof Package) { + if (mDisplayArchives) { + return ((Package) parentElement).getArchives(); + } + + } + + return new Object[0]; + } + + public Object getParent(Object element) { + // TODO Auto-generated method stub + return null; + } + + public boolean hasChildren(Object parentElement) { + if (parentElement instanceof ArrayList<?>) { + return true; + + } else if (parentElement instanceof PkgCategory) { + return true; + + } else if (parentElement instanceof PkgItem) { + List<Package> pkgs = ((PkgItem) parentElement).getUpdatePkgs(); + if (pkgs != null) { + return !pkgs.isEmpty(); + } + + if (mDisplayArchives) { + Archive[] archives = ((PkgItem) parentElement).getArchives(); + return archives.length > 0; + } + } else if (parentElement instanceof Package) { + if (mDisplayArchives) { + return ((Package) parentElement).getArchives().length > 0; + } + } + + return false; + } + + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } + + public void dispose() { + // unused + + } + + public void inputChanged(Viewer arg0, Object arg1, Object arg2) { + // unused + } + } + + private static class PkgCategory { + private final Integer mKey; + private final Object mIconRef; + private final List<PkgItem> mItems = new ArrayList<PkgItem>(); + private String mLabel; + + // When storing by API, key is the API level (>=1), except 0 is tools and 1 is extra/addons. + // When sorting by Source, key is the hash of the source's name. + public final static Integer KEY_TOOLS = Integer.valueOf(0); + public final static Integer KEY_EXTRA = Integer.valueOf(-1); + + public PkgCategory(Integer key, String label, Object iconRef) { + mKey = key; + mLabel = label; + mIconRef = iconRef; + } + + public Integer getKey() { + return mKey; + } + + public String getLabel() { + return mLabel; + } + + public void setLabel(String label) { + mLabel = label; + } + + public Object getIconRef() { + return mIconRef; + } + + public List<PkgItem> getItems() { + return mItems; + } + } + + public enum PkgState { + /** + * Package is locally installed and has no update available on remote sites. + */ + INSTALLED, + + /** + * Package is installed and has an update available. + * In this case, {@link PkgItem#getUpdatePkgs()} provides the list of 1 or more + * packages that can update this {@link PkgItem}. + */ + HAS_UPDATE, + + /** + * There's a new package available on the remote site that isn't + * installed locally. + */ + NEW + } + + public static class PkgItem implements Comparable<PkgItem> { + private final Package mPkg; + private PkgState mState; + private List<Package> mUpdatePkgs; + + public PkgItem(Package pkg, PkgState state) { + mPkg = pkg; + mState = state; + assert mPkg != null; + } + + public boolean isObsolete() { + return mPkg.isObsolete(); + } + + public boolean isSameAs(Package pkg) { + return mPkg.canBeUpdatedBy(pkg) == UpdateInfo.NOT_UPDATE; + } + + /** + * Check whether the 'pkg' argument updates this package. + * If it does, record it as a sub-package. + * Returns true if it was recorded as an update, false otherwise. + */ + public boolean isUpdatedBy(Package pkg) { + if (mPkg.canBeUpdatedBy(pkg) == UpdateInfo.UPDATE) { + if (mUpdatePkgs == null) { + mUpdatePkgs = new ArrayList<Package>(); + } + mUpdatePkgs.add(pkg); + mState = PkgState.HAS_UPDATE; + return true; + } + + return false; + } + + public String getName() { + return mPkg.getListDescription(); + } + + public int getRevision() { + return mPkg.getRevision(); + } + + public String getDescription() { + return mPkg.getDescription(); + } + + public Package getPackage() { + return mPkg; + } + + public PkgState getState() { + return mState; + } + + public SdkSource getSource() { + if (mState == PkgState.NEW) { + return mPkg.getParentSource(); + } else { + return null; + } + } + + public int getApi() { + return mPkg instanceof IPackageVersion ? + ((IPackageVersion) mPkg).getVersion().getApiLevel() : + -1; + } + + public List<Package> getUpdatePkgs() { + return mUpdatePkgs; + } + + public Archive[] getArchives() { + return mPkg.getArchives(); + } + + public int compareTo(PkgItem pkg) { + return getPackage().compareTo(pkg.getPackage()); + } + } + + + + // --- Implementation of ISdkChangeListener --- + + public void onSdkLoaded() { + onSdkReload(); + } + + public void onSdkReload() { + loadPackages(); + } + + public void preInstallHook() { + // nothing to be done for now. + } + + public void postInstallHook() { + // nothing to be done for now. + } + + // --- End of hiding from SWT Designer --- + //$hide<<$ +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java index 6fbf060..0a70162 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RemotePackagesPage.java @@ -1,495 +1,497 @@ -/*
- * Copyright (C) 2009 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.sdkuilib.internal.repository;
-
-
-import com.android.sdklib.internal.repository.Archive;
-import com.android.sdklib.internal.repository.IDescription;
-import com.android.sdklib.internal.repository.Package;
-import com.android.sdklib.internal.repository.SdkAddonSource;
-import com.android.sdklib.internal.repository.SdkSource;
-import com.android.sdklib.internal.repository.SdkSourceCategory;
-import com.android.sdkuilib.repository.ISdkChangeListener;
-
-import org.eclipse.jface.dialogs.IInputValidator;
-import org.eclipse.jface.dialogs.InputDialog;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.jface.viewers.CheckStateChangedEvent;
-import org.eclipse.jface.viewers.CheckboxTreeViewer;
-import org.eclipse.jface.viewers.ICheckStateListener;
-import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.ITreeContentProvider;
-import org.eclipse.jface.viewers.ITreeSelection;
-import org.eclipse.jface.window.Window;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.ControlAdapter;
-import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Group;
-import org.eclipse.swt.widgets.Label;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeColumn;
-
-import java.util.ArrayList;
-
-
-public class RemotePackagesPage extends Composite implements ISdkChangeListener {
-
- private final UpdaterData mUpdaterData;
-
- private CheckboxTreeViewer mTreeViewerSources;
- private Tree mTreeSources;
- private TreeColumn mColumnSource;
- private Button mUpdateOnlyCheckBox;
- private Group mDescriptionContainer;
- private Button mAddSiteButton;
- private Button mDeleteSiteButton;
- private Button mRefreshButton;
- private Button mInstallSelectedButton;
- private Label mDescriptionLabel;
- private Label mSdkLocLabel;
-
-
-
- /**
- * Create the composite.
- * @param parent The parent of the composite.
- * @param updaterData An instance of {@link UpdaterData}.
- */
- RemotePackagesPage(Composite parent, UpdaterData updaterData) {
- super(parent, SWT.BORDER);
-
- mUpdaterData = updaterData;
-
- createContents(this);
- postCreate(); //$hide$
- }
-
- private void createContents(Composite parent) {
- parent.setLayout(new GridLayout(5, false));
-
- mSdkLocLabel = new Label(parent, SWT.NONE);
- mSdkLocLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 5, 1));
- mSdkLocLabel.setText("SDK Location: " +
- (mUpdaterData.getOsSdkRoot() != null ? mUpdaterData.getOsSdkRoot()
- : "<unknown>"));
-
- mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER);
- mTreeViewerSources.addCheckStateListener(new ICheckStateListener() {
- public void checkStateChanged(CheckStateChangedEvent event) {
- onTreeCheckStateChanged(event); //$hide$
- }
- });
- mTreeSources = mTreeViewerSources.getTree();
- mTreeSources.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onTreeSelected(); //$hide$
- }
- });
- mTreeSources.setHeaderVisible(true);
- mTreeSources.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
-
- mColumnSource = new TreeColumn(mTreeSources, SWT.NONE);
- mColumnSource.setWidth(289);
- mColumnSource.setText("Packages available for download");
-
- mDescriptionContainer = new Group(parent, SWT.NONE);
- mDescriptionContainer.setLayout(new GridLayout(1, false));
- mDescriptionContainer.setText("Description");
- mDescriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 5, 1));
-
- mDescriptionLabel = new Label(mDescriptionContainer, SWT.NONE);
- mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
- mDescriptionLabel.setText("Line1\nLine2\nLine3"); //$NON-NLS-1$
-
- mAddSiteButton = new Button(parent, SWT.NONE);
- mAddSiteButton.setText("Add Add-on Site...");
- mAddSiteButton.setToolTipText("Allows you to enter a new add-on site. " +
- "Such site can only contribute add-ons and extra packages.");
- mAddSiteButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onAddSiteSelected(); //$hide$
- }
- });
-
- mDeleteSiteButton = new Button(parent, SWT.NONE);
- mDeleteSiteButton.setText("Delete Add-on Site...");
- mDeleteSiteButton.setToolTipText("Allows you to remove an add-on site. " +
- "Built-in default sites cannot be removed.");
- mDeleteSiteButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onRemoveSiteSelected(); //$hide$
- }
- });
-
- mUpdateOnlyCheckBox = new Button(parent, SWT.CHECK);
- mUpdateOnlyCheckBox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1));
- mUpdateOnlyCheckBox.setText("Display updates only");
- mUpdateOnlyCheckBox.setToolTipText("When selected, only compatible non-obsolete update packages are shown in the list above.");
- mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());
- mUpdateOnlyCheckBox.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent arg0) {
- onShowUpdateOnly(); //$hide$
- }
- });
-
- mRefreshButton = new Button(parent, SWT.NONE);
- mRefreshButton.setText("Refresh");
- mRefreshButton.setToolTipText("Refreshes the list of packages from open sites.");
- mRefreshButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onRefreshSelected(); //$hide$
- }
- });
-
- mInstallSelectedButton = new Button(parent, SWT.NONE);
- mInstallSelectedButton.setText("Install Selected");
- mInstallSelectedButton.setToolTipText("Allows you to review all selected packages " +
- "and install them.");
- mInstallSelectedButton.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- onInstallSelectedArchives(); //$hide$
- }
- });
- }
-
- @Override
- public void dispose() {
- mUpdaterData.removeListener(this);
- super.dispose();
- }
-
- @Override
- protected void checkSubclass() {
- // Disable the check that prevents subclassing of SWT components
- }
-
- // -- Start of internal part ----------
- // Hide everything down-below from SWT designer
- //$hide>>$
-
- /**
- * Called by the constructor right after {@link #createContents(Composite)}.
- */
- private void postCreate() {
- mUpdaterData.addListeners(this);
- adjustColumnsWidth();
- updateButtonsState();
- }
-
- /**
- * Adds a listener to adjust the columns width when the parent is resized.
- * <p/>
- * If we need something more fancy, we might want to use this:
- * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
- */
- private void adjustColumnsWidth() {
- // Add a listener to resize the column to the full width of the table
- ControlAdapter resizer = new ControlAdapter() {
- @Override
- public void controlResized(ControlEvent e) {
- Rectangle r = mTreeSources.getClientArea();
- mColumnSource.setWidth(r.width);
- }
- };
-
- mTreeSources.addControlListener(resizer);
- resizer.controlResized(null);
- }
-
- /**
- * Called when an item in the package table viewer is selected.
- * If the items is an {@link IDescription} (as it should), this will display its long
- * description in the description area. Otherwise when the item is not of the expected
- * type or there is no selection, it empties the description area.
- */
- private void onTreeSelected() {
- updateButtonsState();
-
- ISelection sel = mTreeViewerSources.getSelection();
- if (sel instanceof ITreeSelection) {
- Object elem = ((ITreeSelection) sel).getFirstElement();
- if (elem instanceof IDescription) {
- mDescriptionLabel.setText(((IDescription) elem).getLongDescription());
- mDescriptionContainer.layout(true);
- return;
- }
- }
- mDescriptionLabel.setText(""); //$NON-NLS1-$
- }
-
- /**
- * Handle checking and unchecking of the tree items.
- *
- * When unchecking, all sub-tree items checkboxes are cleared too.
- * When checking a source, all of its packages are checked too.
- * When checking a package, only its compatible archives are checked.
- */
- private void onTreeCheckStateChanged(CheckStateChangedEvent event) {
- updateButtonsState();
-
- boolean b = event.getChecked();
- Object elem = event.getElement(); // Will be Archive or Package or RepoSource
-
- assert event.getSource() == mTreeViewerSources;
-
- // when deselecting, we just deselect all children too
- if (b == false) {
- mTreeViewerSources.setSubtreeChecked(elem, b);
- return;
- }
-
- ITreeContentProvider provider =
- (ITreeContentProvider) mTreeViewerSources.getContentProvider();
-
- // When selecting, we want to only select compatible archives
- // and expand the super nodes.
- expandItem(elem, provider);
- }
-
- private void expandItem(Object elem, ITreeContentProvider provider) {
- if (elem instanceof SdkSource || elem instanceof SdkSourceCategory) {
- mTreeViewerSources.setExpandedState(elem, true);
- for (Object pkg : provider.getChildren(elem)) {
- mTreeViewerSources.setChecked(pkg, true);
- expandItem(pkg, provider);
- }
- } else if (elem instanceof Package) {
- selectCompatibleArchives(elem, provider);
- }
- }
-
- private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
- for (Object archive : provider.getChildren(pkg)) {
- if (archive instanceof Archive) {
- mTreeViewerSources.setChecked(archive, ((Archive) archive).isCompatible());
- }
- }
- }
-
- private void onShowUpdateOnly() {
- SettingsController controller = mUpdaterData.getSettingsController();
- controller.setShowUpdateOnly(mUpdateOnlyCheckBox.getSelection());
- controller.saveSettings();
-
- // Get the list of selected archives
- ArrayList<Archive> archives = new ArrayList<Archive>();
- for (Object element : mTreeViewerSources.getCheckedElements()) {
- if (element instanceof Archive) {
- archives.add((Archive) element);
- }
- // Deselect them all
- mTreeViewerSources.setChecked(element, false);
- }
-
- mTreeViewerSources.refresh();
-
- // Now reselect those that still exist in the tree but only if they
- // are compatible archives
- for (Archive a : archives) {
- if (a.isCompatible() && mTreeViewerSources.setChecked(a, true)) {
- // If we managed to select the archive, also select the parent package.
- // Technically we should only select the parent package if *all* the
- // compatible archives children are selected. In practice we'll rarely
- // have more than one compatible archive per package.
- mTreeViewerSources.setChecked(a.getParentPackage(), true);
- }
- }
-
- updateButtonsState();
- }
-
- private void onInstallSelectedArchives() {
- ArrayList<Archive> archives = new ArrayList<Archive>();
- for (Object element : mTreeViewerSources.getCheckedElements()) {
- if (element instanceof Archive) {
- archives.add((Archive) element);
- }
- }
-
- if (mUpdaterData != null) {
- mUpdaterData.updateOrInstallAll_WithGUI(
- archives,
- mUpdateOnlyCheckBox.getSelection() /* includeObsoletes */);
- }
- }
-
- private void onAddSiteSelected() {
-
- final SdkSource[] knowSources = mUpdaterData.getSources().getAllSources();
- String title = "Add Add-on Site URL";
-
- String msg =
- "This dialog lets you add the URL of a new add-on site.\n" +
- "\n" +
- "An add-on site can only provide new add-ons or \"user\" packages.\n" +
- "Add-on sites cannot provide standard Android platforms, docs or samples packages.\n" +
- "Inserting a URL here will not allow you to clone an official Android repository.\n" +
- "\n" +
- "Please enter the URL of the repository.xml for the new add-on site:";
-
- InputDialog dlg = new InputDialog(getShell(), title, msg, null, new IInputValidator() {
- public String isValid(String newText) {
-
- newText = newText == null ? null : newText.trim();
-
- if (newText == null || newText.length() == 0) {
- return "Error: URL field is empty. Please enter a URL.";
- }
-
- // A URL should have one of the following prefixes
- if (!newText.startsWith("file://") && //$NON-NLS-1$
- !newText.startsWith("ftp://") && //$NON-NLS-1$
- !newText.startsWith("http://") && //$NON-NLS-1$
- !newText.startsWith("https://")) { //$NON-NLS-1$
- return "Error: The URL must start by one of file://, ftp://, http:// or https://";
- }
-
- // Reject URLs that are already in the source list.
- // URLs are generally case-insensitive (except for file:// where it all depends
- // on the current OS so we'll ignore this case.)
- for (SdkSource s : knowSources) {
- if (newText.equalsIgnoreCase(s.getUrl())) {
- return "Error : This site is already listed.";
- }
- }
-
- return null;
- }
- });
-
- if (dlg.open() == Window.OK) {
- String url = dlg.getValue().trim();
- mUpdaterData.getSources().add(
- SdkSourceCategory.USER_ADDONS,
- new SdkAddonSource(url, null/*uiName*/));
- onRefreshSelected();
- }
- }
-
- private void onRemoveSiteSelected() {
- boolean changed = false;
-
- ISelection sel = mTreeViewerSources.getSelection();
- if (mUpdaterData != null && sel instanceof ITreeSelection) {
- for (Object c : ((ITreeSelection) sel).toList()) {
- if (c instanceof SdkSource) {
- SdkSource source = (SdkSource) c;
-
- if (mUpdaterData.getSources().hasSourceUrl(
- SdkSourceCategory.USER_ADDONS, source)) {
- String title = "Delete Add-on Site?";
-
- String msg = String.format("Are you sure you want to delete the add-on site '%1$s'?",
- source.getUrl());
-
- if (MessageDialog.openQuestion(getShell(), title, msg)) {
- mUpdaterData.getSources().remove(source);
- changed = true;
- }
- }
- }
- }
- }
-
- if (changed) {
- onRefreshSelected();
- }
- }
-
- private void onRefreshSelected() {
- if (mUpdaterData != null) {
- mUpdaterData.refreshSources(false /*forceFetching*/);
- }
- mTreeViewerSources.refresh();
- updateButtonsState();
- }
-
- private void updateButtonsState() {
- // We install archives, so there should be at least one checked archive.
- // Having sites or packages checked does not count.
- boolean hasCheckedArchive = false;
- Object[] checked = mTreeViewerSources.getCheckedElements();
- if (checked != null) {
- for (Object c : checked) {
- if (c instanceof Archive) {
- hasCheckedArchive = true;
- break;
- }
- }
- }
-
- // Is there a selected site Source?
- boolean hasSelectedUserSource = false;
- ISelection sel = mTreeViewerSources.getSelection();
- if (sel instanceof ITreeSelection) {
- for (Object c : ((ITreeSelection) sel).toList()) {
- if (c instanceof SdkSource &&
- mUpdaterData.getSources().hasSourceUrl(
- SdkSourceCategory.USER_ADDONS, (SdkSource) c)) {
- hasSelectedUserSource = true;
- break;
- }
- }
- }
-
- mAddSiteButton.setEnabled(true);
- mDeleteSiteButton.setEnabled(hasSelectedUserSource);
- mRefreshButton.setEnabled(true);
- mInstallSelectedButton.setEnabled(hasCheckedArchive);
-
- // set value on the show only update checkbox
- mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());
- }
-
- // --- Implementation of ISdkChangeListener ---
-
- public void onSdkLoaded() {
- onSdkReload();
- }
-
- public void onSdkReload() {
- RepoSourcesAdapter sources = mUpdaterData.getSourcesAdapter();
- mTreeViewerSources.setContentProvider(sources.getContentProvider());
- mTreeViewerSources.setLabelProvider( sources.getLabelProvider());
- mTreeViewerSources.setInput(sources);
- onTreeSelected();
- }
-
- public void preInstallHook() {
- // nothing to be done for now.
- }
-
- public void postInstallHook() {
- // nothing to be done for now.
- }
-
- // End of hiding from SWT Designer
- //$hide<<$
-}
+/* + * Copyright (C) 2009 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.sdkuilib.internal.repository; + + +import com.android.sdklib.internal.repository.Archive; +import com.android.sdklib.internal.repository.IDescription; +import com.android.sdklib.internal.repository.Package; +import com.android.sdklib.internal.repository.SdkAddonSource; +import com.android.sdklib.internal.repository.SdkSource; +import com.android.sdklib.internal.repository.SdkSourceCategory; +import com.android.sdkuilib.repository.ISdkChangeListener; + +import org.eclipse.jface.dialogs.IInputValidator; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +import java.util.ArrayList; + +/** + * Page that displays remote repository & add-ons sources and let the user + * select packages for installation. + */ +public class RemotePackagesPage extends Composite implements ISdkChangeListener { + + private final UpdaterData mUpdaterData; + + private CheckboxTreeViewer mTreeViewerSources; + private Tree mTreeSources; + private TreeColumn mColumnSource; + private Button mUpdateOnlyCheckBox; + private Group mDescriptionContainer; + private Button mAddSiteButton; + private Button mDeleteSiteButton; + private Button mRefreshButton; + private Button mInstallSelectedButton; + private Label mDescriptionLabel; + private Label mSdkLocLabel; + + + + /** + * Create the composite. + * @param parent The parent of the composite. + * @param updaterData An instance of {@link UpdaterData}. + */ + RemotePackagesPage(Composite parent, UpdaterData updaterData) { + super(parent, SWT.BORDER); + + mUpdaterData = updaterData; + + createContents(this); + postCreate(); //$hide$ + } + + private void createContents(Composite parent) { + parent.setLayout(new GridLayout(5, false)); + + mSdkLocLabel = new Label(parent, SWT.NONE); + mSdkLocLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false, 5, 1)); + mSdkLocLabel.setText("SDK Location: " + + (mUpdaterData.getOsSdkRoot() != null ? mUpdaterData.getOsSdkRoot() + : "<unknown>")); + + mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER); + mTreeViewerSources.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent event) { + onTreeCheckStateChanged(event); //$hide$ + } + }); + mTreeSources = mTreeViewerSources.getTree(); + mTreeSources.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onTreeSelected(); //$hide$ + } + }); + mTreeSources.setHeaderVisible(true); + mTreeSources.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1)); + + mColumnSource = new TreeColumn(mTreeSources, SWT.NONE); + mColumnSource.setWidth(289); + mColumnSource.setText("Packages available for download"); + + mDescriptionContainer = new Group(parent, SWT.NONE); + mDescriptionContainer.setLayout(new GridLayout(1, false)); + mDescriptionContainer.setText("Description"); + mDescriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 5, 1)); + + mDescriptionLabel = new Label(mDescriptionContainer, SWT.NONE); + mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + mDescriptionLabel.setText("Line1\nLine2\nLine3"); //$NON-NLS-1$ + + mAddSiteButton = new Button(parent, SWT.NONE); + mAddSiteButton.setText("Add Add-on Site..."); + mAddSiteButton.setToolTipText("Allows you to enter a new add-on site. " + + "Such site can only contribute add-ons and extra packages."); + mAddSiteButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onAddSiteSelected(); //$hide$ + } + }); + + mDeleteSiteButton = new Button(parent, SWT.NONE); + mDeleteSiteButton.setText("Delete Add-on Site..."); + mDeleteSiteButton.setToolTipText("Allows you to remove an add-on site. " + + "Built-in default sites cannot be removed."); + mDeleteSiteButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onRemoveSiteSelected(); //$hide$ + } + }); + + mUpdateOnlyCheckBox = new Button(parent, SWT.CHECK); + mUpdateOnlyCheckBox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1)); + mUpdateOnlyCheckBox.setText("Display updates only"); + mUpdateOnlyCheckBox.setToolTipText("When selected, only compatible non-obsolete update packages are shown in the list above."); + mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly()); + mUpdateOnlyCheckBox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent arg0) { + onShowUpdateOnly(); //$hide$ + } + }); + + mRefreshButton = new Button(parent, SWT.NONE); + mRefreshButton.setText("Refresh"); + mRefreshButton.setToolTipText("Refreshes the list of packages from open sites."); + mRefreshButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onRefreshSelected(); //$hide$ + } + }); + + mInstallSelectedButton = new Button(parent, SWT.NONE); + mInstallSelectedButton.setText("Install Selected"); + mInstallSelectedButton.setToolTipText("Allows you to review all selected packages " + + "and install them."); + mInstallSelectedButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onInstallSelectedArchives(); //$hide$ + } + }); + } + + @Override + public void dispose() { + mUpdaterData.removeListener(this); + super.dispose(); + } + + @Override + protected void checkSubclass() { + // Disable the check that prevents subclassing of SWT components + } + + // -- Start of internal part ---------- + // Hide everything down-below from SWT designer + //$hide>>$ + + /** + * Called by the constructor right after {@link #createContents(Composite)}. + */ + private void postCreate() { + mUpdaterData.addListeners(this); + adjustColumnsWidth(); + updateButtonsState(); + } + + /** + * Adds a listener to adjust the columns width when the parent is resized. + * <p/> + * If we need something more fancy, we might want to use this: + * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co + */ + private void adjustColumnsWidth() { + // Add a listener to resize the column to the full width of the table + ControlAdapter resizer = new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = mTreeSources.getClientArea(); + mColumnSource.setWidth(r.width); + } + }; + + mTreeSources.addControlListener(resizer); + resizer.controlResized(null); + } + + /** + * Called when an item in the package table viewer is selected. + * If the items is an {@link IDescription} (as it should), this will display its long + * description in the description area. Otherwise when the item is not of the expected + * type or there is no selection, it empties the description area. + */ + private void onTreeSelected() { + updateButtonsState(); + + ISelection sel = mTreeViewerSources.getSelection(); + if (sel instanceof ITreeSelection) { + Object elem = ((ITreeSelection) sel).getFirstElement(); + if (elem instanceof IDescription) { + mDescriptionLabel.setText(((IDescription) elem).getLongDescription()); + mDescriptionContainer.layout(true); + return; + } + } + mDescriptionLabel.setText(""); //$NON-NLS1-$ + } + + /** + * Handle checking and unchecking of the tree items. + * + * When unchecking, all sub-tree items checkboxes are cleared too. + * When checking a source, all of its packages are checked too. + * When checking a package, only its compatible archives are checked. + */ + private void onTreeCheckStateChanged(CheckStateChangedEvent event) { + updateButtonsState(); + + boolean b = event.getChecked(); + Object elem = event.getElement(); // Will be Archive or Package or RepoSource + + assert event.getSource() == mTreeViewerSources; + + // when deselecting, we just deselect all children too + if (b == false) { + mTreeViewerSources.setSubtreeChecked(elem, b); + return; + } + + ITreeContentProvider provider = + (ITreeContentProvider) mTreeViewerSources.getContentProvider(); + + // When selecting, we want to only select compatible archives + // and expand the super nodes. + expandItem(elem, provider); + } + + private void expandItem(Object elem, ITreeContentProvider provider) { + if (elem instanceof SdkSource || elem instanceof SdkSourceCategory) { + mTreeViewerSources.setExpandedState(elem, true); + for (Object pkg : provider.getChildren(elem)) { + mTreeViewerSources.setChecked(pkg, true); + expandItem(pkg, provider); + } + } else if (elem instanceof Package) { + selectCompatibleArchives(elem, provider); + } + } + + private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) { + for (Object archive : provider.getChildren(pkg)) { + if (archive instanceof Archive) { + mTreeViewerSources.setChecked(archive, ((Archive) archive).isCompatible()); + } + } + } + + private void onShowUpdateOnly() { + SettingsController controller = mUpdaterData.getSettingsController(); + controller.setShowUpdateOnly(mUpdateOnlyCheckBox.getSelection()); + controller.saveSettings(); + + // Get the list of selected archives + ArrayList<Archive> archives = new ArrayList<Archive>(); + for (Object element : mTreeViewerSources.getCheckedElements()) { + if (element instanceof Archive) { + archives.add((Archive) element); + } + // Deselect them all + mTreeViewerSources.setChecked(element, false); + } + + mTreeViewerSources.refresh(); + + // Now reselect those that still exist in the tree but only if they + // are compatible archives + for (Archive a : archives) { + if (a.isCompatible() && mTreeViewerSources.setChecked(a, true)) { + // If we managed to select the archive, also select the parent package. + // Technically we should only select the parent package if *all* the + // compatible archives children are selected. In practice we'll rarely + // have more than one compatible archive per package. + mTreeViewerSources.setChecked(a.getParentPackage(), true); + } + } + + updateButtonsState(); + } + + private void onInstallSelectedArchives() { + ArrayList<Archive> archives = new ArrayList<Archive>(); + for (Object element : mTreeViewerSources.getCheckedElements()) { + if (element instanceof Archive) { + archives.add((Archive) element); + } + } + + if (mUpdaterData != null) { + mUpdaterData.updateOrInstallAll_WithGUI( + archives, + mUpdateOnlyCheckBox.getSelection() /* includeObsoletes */); + } + } + + private void onAddSiteSelected() { + final SdkSource[] knowSources = mUpdaterData.getSources().getAllSources(); + String title = "Add Add-on Site URL"; + + String msg = + "This dialog lets you add the URL of a new add-on site.\n" + + "\n" + + "An add-on site can only provide new add-ons or \"user\" packages.\n" + + "Add-on sites cannot provide standard Android platforms, docs or samples packages.\n" + + "Inserting a URL here will not allow you to clone an official Android repository.\n" + + "\n" + + "Please enter the URL of the repository.xml for the new add-on site:"; + + InputDialog dlg = new InputDialog(getShell(), title, msg, null, new IInputValidator() { + public String isValid(String newText) { + + newText = newText == null ? null : newText.trim(); + + if (newText == null || newText.length() == 0) { + return "Error: URL field is empty. Please enter a URL."; + } + + // A URL should have one of the following prefixes + if (!newText.startsWith("file://") && //$NON-NLS-1$ + !newText.startsWith("ftp://") && //$NON-NLS-1$ + !newText.startsWith("http://") && //$NON-NLS-1$ + !newText.startsWith("https://")) { //$NON-NLS-1$ + return "Error: The URL must start by one of file://, ftp://, http:// or https://"; + } + + // Reject URLs that are already in the source list. + // URLs are generally case-insensitive (except for file:// where it all depends + // on the current OS so we'll ignore this case.) + for (SdkSource s : knowSources) { + if (newText.equalsIgnoreCase(s.getUrl())) { + return "Error : This site is already listed."; + } + } + + return null; + } + }); + + if (dlg.open() == Window.OK) { + String url = dlg.getValue().trim(); + mUpdaterData.getSources().add( + SdkSourceCategory.USER_ADDONS, + new SdkAddonSource(url, null/*uiName*/)); + onRefreshSelected(); + } + } + + private void onRemoveSiteSelected() { + boolean changed = false; + + ISelection sel = mTreeViewerSources.getSelection(); + if (mUpdaterData != null && sel instanceof ITreeSelection) { + for (Object c : ((ITreeSelection) sel).toList()) { + if (c instanceof SdkSource) { + SdkSource source = (SdkSource) c; + + if (mUpdaterData.getSources().hasSourceUrl( + SdkSourceCategory.USER_ADDONS, source)) { + String title = "Delete Add-on Site?"; + + String msg = String.format("Are you sure you want to delete the add-on site '%1$s'?", + source.getUrl()); + + if (MessageDialog.openQuestion(getShell(), title, msg)) { + mUpdaterData.getSources().remove(source); + changed = true; + } + } + } + } + } + + if (changed) { + onRefreshSelected(); + } + } + + private void onRefreshSelected() { + if (mUpdaterData != null) { + mUpdaterData.refreshSources(false /*forceFetching*/); + } + mTreeViewerSources.refresh(); + updateButtonsState(); + } + + private void updateButtonsState() { + // We install archives, so there should be at least one checked archive. + // Having sites or packages checked does not count. + boolean hasCheckedArchive = false; + Object[] checked = mTreeViewerSources.getCheckedElements(); + if (checked != null) { + for (Object c : checked) { + if (c instanceof Archive) { + hasCheckedArchive = true; + break; + } + } + } + + // Is there a selected site Source? + boolean hasSelectedUserSource = false; + ISelection sel = mTreeViewerSources.getSelection(); + if (sel instanceof ITreeSelection) { + for (Object c : ((ITreeSelection) sel).toList()) { + if (c instanceof SdkSource && + mUpdaterData.getSources().hasSourceUrl( + SdkSourceCategory.USER_ADDONS, (SdkSource) c)) { + hasSelectedUserSource = true; + break; + } + } + } + + mAddSiteButton.setEnabled(true); + mDeleteSiteButton.setEnabled(hasSelectedUserSource); + mRefreshButton.setEnabled(true); + mInstallSelectedButton.setEnabled(hasCheckedArchive); + + // set value on the show only update checkbox + mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly()); + } + + // --- Implementation of ISdkChangeListener --- + + public void onSdkLoaded() { + onSdkReload(); + } + + public void onSdkReload() { + RepoSourcesAdapter sources = mUpdaterData.getSourcesAdapter(); + mTreeViewerSources.setContentProvider(sources.getContentProvider()); + mTreeViewerSources.setLabelProvider( sources.getLabelProvider()); + mTreeViewerSources.setInput(sources); + onTreeSelected(); + } + + public void preInstallHook() { + // nothing to be done for now. + } + + public void postInstallHook() { + // nothing to be done for now. + } + + // End of hiding from SWT Designer + //$hide<<$ +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java index 8722e02..54bc068 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/RepoSourcesAdapter.java @@ -316,13 +316,13 @@ public class RepoSourcesAdapter { // get the installed packages
Package[] installedPackages = mUpdaterData.getInstalledPackages();
+ // we'll populate this package list with either upgrades or new packages.
ArrayList<Package> filteredList = new ArrayList<Package>();
// for each remote packages, we look for an existing version.
// If no existing version -> add to the list
// if existing version but with older revision -> add it to the list
for (Package remotePkg : remotePackages) {
- boolean newPkg = true;
// Obsolete packages are not offered as updates.
if (remotePkg.isObsolete()) {
@@ -332,20 +332,27 @@ public class RepoSourcesAdapter { // For all potential packages, we also make sure that there's an archive for
// the current platform, or we simply skip them.
if (remotePkg.hasCompatibleArchive()) {
- for (Package installedPkg : installedPackages) {
+ boolean keepPkg = true;
+
+ nextPkg: for (Package installedPkg : installedPackages) {
UpdateInfo info = installedPkg.canBeUpdatedBy(remotePkg);
- if (info == UpdateInfo.UPDATE) {
- filteredList.add(remotePkg);
- newPkg = false;
- break; // there shouldn't be 2 revisions of the same package
- } else if (info != UpdateInfo.INCOMPATIBLE) {
- newPkg = false;
- break; // there shouldn't be 2 revisions of the same package
+ switch(info) {
+ case UPDATE:
+ // The remote package is an update to an existing one.
+ // We're done looking.
+ keepPkg = true;
+ break nextPkg;
+ case NOT_UPDATE:
+ // The remote package is the same as one that is already installed.
+ keepPkg = false;
+ break;
+ case INCOMPATIBLE:
+ // We can't compare and decide on incompatible things.
+ break;
}
}
- // if we have not found the same package, then we add it (it's a new package)
- if (newPkg) {
+ if (keepPkg) {
filteredList.add(remotePkg);
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateNoWindow.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateNoWindow.java index 2279b2d..6d5b5d4 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateNoWindow.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdateNoWindow.java @@ -28,11 +28,6 @@ import java.util.Properties; /** * Performs an update using only a non-interactive console output with no GUI. - * <p/> - * TODO: It may be useful in the future to let the filter specify packages names - * rather than package types, typically to let the user upgrade to a new platform. - * This can be achieved easily by simply allowing package names in the pkgFilter - * argument. */ public class UpdateNoWindow { @@ -111,6 +106,15 @@ public class UpdateNoWindow { mUpdaterData.updateOrInstallAll_NoGUI(pkgFilter, includeObsoletes, dryMode); } + /** + * Lists remote packages available for install using 'android update sdk --no-ui'. + * + * @param includeObsoletes True to also list and install obsolete packages. + */ + public void listRemotePackages(boolean includeObsoletes) { + mUpdaterData.listRemotePackages_NoGUI(includeObsoletes); + } + // ----- /** @@ -170,45 +174,52 @@ public class UpdateNoWindow { /** * Sets the description in the current task dialog. */ - public void setDescription(String descriptionFormat, Object...args) { + public void setDescription(String format, Object...args) { String last = mLastDesc; - String line = String.format(" " + descriptionFormat, args); + String line = String.format(" " + format, args); //$NON-NLS-1$ // If the description contains a %, it generally indicates a recurring // progress so we want a \r at the end. - if (line.indexOf('%') > -1) { - if (mLastProgressBase != null && line.startsWith(mLastProgressBase)) { - line = " " + line.substring(mLastProgressBase.length()); + int pos = line.indexOf('%'); + if (pos > -1) { + String base = line.trim(); + if (mLastProgressBase != null && base.startsWith(mLastProgressBase)) { + line = " " + base.substring(mLastProgressBase.length()); //$NON-NLS-1$ } - line += "\r"; + line += '\r'; } else { - mLastProgressBase = line; - line += "\n"; + mLastProgressBase = line.trim(); + line += '\n'; } // Skip line if it's the same as the last one. - if (last != null && last.equals(line)) { + if (last != null && last.equals(line.trim())) { return; } - mLastDesc = line; + mLastDesc = line.trim(); // If the last line terminated with a \r but the new one doesn't, we need to // insert a \n to avoid erasing the previous line. if (last != null && - last.endsWith("\r") && - !line.endsWith("\r")) { - line = "\n" + line; + last.endsWith("\r") && //$NON-NLS-1$ + !line.endsWith("\r")) { //$NON-NLS-1$ + line = '\n' + line; } - mSdkLog.printf("%s", line); + mSdkLog.printf("%s", line); //$NON-NLS-1$ } - /** - * Sets the description in the current task dialog. - */ - public void setResult(String resultFormat, Object...args) { - setDescription(resultFormat, args); + public void log(String format, Object...args) { + setDescription(" " + format, args); //$NON-NLS-1$ + } + + public void logError(String format, Object...args) { + setDescription(format, args); + } + + public void logVerbose(String format, Object...args) { + // The ConsoleTask does not display verbose log messages. } /** @@ -327,12 +338,20 @@ public class UpdateNoWindow { return mRoot.isCancelRequested(); } - public void setDescription(String descriptionFormat, Object... args) { - mRoot.setDescription(descriptionFormat, args); + public void setDescription(String format, Object... args) { + mRoot.setDescription(format, args); + } + + public void log(String format, Object... args) { + mRoot.log(format, args); + } + + public void logError(String format, Object... args) { + mRoot.logError(format, args); } - public void setResult(String resultFormat, Object... args) { - mRoot.setResult(resultFormat, args); + public void logVerbose(String format, Object... args) { + mRoot.logVerbose(format, args); } public void setProgressMax(int max) { diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java index b0160d9..1a3b5cb 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterData.java @@ -44,6 +44,7 @@ import com.android.sdklib.internal.repository.AddonsListFetcher.Site; import com.android.sdklib.repository.SdkAddonConstants;
import com.android.sdklib.repository.SdkAddonsListConstants;
import com.android.sdklib.repository.SdkRepoConstants;
+import com.android.sdklib.util.SparseIntArray;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.repository.ISdkChangeListener;
@@ -437,14 +438,14 @@ class UpdaterData implements IUpdaterData { // This archive depends on a missing archive.
// We shouldn't get here.
// Skip it.
- monitor.setResult("Skipping '%1$s'; it depends on a missing package.",
+ monitor.log("Skipping '%1$s'; it depends on a missing package.",
archive.getParentPackage().getShortDescription());
continue nextArchive;
} else if (!installedArchives.contains(na)) {
// This archive depends on another one that was not installed.
// We shouldn't get here.
// Skip it.
- monitor.setResult("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
+ monitor.logError("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
archive.getParentPackage().getShortDescription(),
adep.getShortDescription());
continue nextArchive;
@@ -499,7 +500,7 @@ class UpdaterData implements IUpdaterData { baos.toString());
}
- monitor.setResult(msg);
+ monitor.log(msg);
mSdkLog.error(t, msg);
} finally {
@@ -514,10 +515,10 @@ class UpdaterData implements IUpdaterData { // Update the USB vendor ids for adb
try {
mSdkManager.updateAdb();
- monitor.setResult("Updated ADB to support the USB devices declared in the SDK add-ons.");
+ monitor.log("Updated ADB to support the USB devices declared in the SDK add-ons.");
} catch (Exception e) {
mSdkLog.error(e, "Update ADB failed");
- monitor.setResult("failed to update adb to support the USB devices declared in the SDK add-ons.");
+ monitor.logError("failed to update adb to support the USB devices declared in the SDK add-ons.");
}
}
@@ -709,23 +710,17 @@ class UpdaterData implements IUpdaterData { }
/**
- * Tries to update all the *existing* local packages.
- * This version is intended to run without a GUI and
- * only outputs to the current {@link ISdkLog}.
+ * Fetches all archives available on the known remote sources.
*
- * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages
- * we can update. A null or empty list means to update everything possible.
- * @param includeObsoletes True to also list and install obsolete packages.
- * @param dryMode True to check what would be updated/installed but do not actually
- * download or install anything.
+ * Used by {@link UpdaterData#listRemotePackages_NoGUI} and
+ * {@link UpdaterData#updateOrInstallAll_NoGUI}.
+ *
+ * @param includeObsoletes True to also list obsolete packages.
+ * @return A list of potential {@link ArchiveInfo} to install.
*/
- @SuppressWarnings("unchecked")
- public void updateOrInstallAll_NoGUI(
- Collection<String> pkgFilter,
- boolean includeObsoletes,
- boolean dryMode) {
-
+ private List<ArchiveInfo> getRemoteArchives_NoGUI(boolean includeObsoletes) {
refreshSources(true);
+ loadRemoteAddonsList();
UpdaterLogic ul = new UpdaterLogic(this);
List<ArchiveInfo> archives = ul.computeUpdates(
@@ -734,7 +729,6 @@ class UpdaterData implements IUpdaterData { getLocalSdkParser().getPackages(),
includeObsoletes);
- loadRemoteAddonsList();
ul.addNewPlatforms(
archives,
getSources(),
@@ -742,66 +736,81 @@ class UpdaterData implements IUpdaterData { includeObsoletes);
Collections.sort(archives);
+ return archives;
+ }
+
+ /**
+ * Lists remote packages available for install using
+ * {@link UpdaterData#updateOrInstallAll_NoGUI}.
+ *
+ * @param includeObsoletes True to also list obsolete packages.
+ */
+ public void listRemotePackages_NoGUI(boolean includeObsoletes) {
+
+ List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeObsoletes);
+
+ mSdkLog.printf("Packages available for installation or update: %1$d\n", archives.size());
+
+ int index = 1;
+ for (ArchiveInfo ai : archives) {
+ Archive a = ai.getNewArchive();
+ if (a != null) {
+ Package p = a.getParentPackage();
+ if (p != null) {
+ mSdkLog.printf("%1$ 4d- %2$s\n",
+ index,
+ p.getShortDescription());
+ index++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Tries to update all the *existing* local packages.
+ * This version is intended to run without a GUI and
+ * only outputs to the current {@link ISdkLog}.
+ *
+ * @param pkgFilter A list of {@link SdkRepoConstants#NODES} to limit the type of packages
+ * we can update. A null or empty list means to update everything possible.
+ * @param includeObsoletes True to also list and install obsolete packages.
+ * @param dryMode True to check what would be updated/installed but do not actually
+ * download or install anything.
+ */
+ public void updateOrInstallAll_NoGUI(
+ Collection<String> pkgFilter,
+ boolean includeObsoletes,
+ boolean dryMode) {
+
+ List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeObsoletes);
// Filter the selected archives to only keep the ones matching the filter
if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) {
- // Map filter types to an SdkRepository Package type.
+ // Map filter types to an SdkRepository Package type,
+ // e.g. create a map "platform" => PlatformPackage.class
HashMap<String, Class<? extends Package>> pkgMap =
new HashMap<String, Class<? extends Package>>();
- // Automatically find the classes matching the node names
- ClassLoader classLoader = getClass().getClassLoader();
- String basePackage = Package.class.getPackage().getName();
- for (String node : SdkRepoConstants.NODES) {
- // Capitalize the name
- String name = node.substring(0, 1).toUpperCase() + node.substring(1);
-
- // We can have one dash at most in a name. If it's present, we'll try
- // with the dash or with the next letter capitalized.
- int dash = name.indexOf('-');
- if (dash > 0) {
- name = name.replaceFirst("-", "");
- }
+ mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES);
+ mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES);
- for (int alternatives = 0; alternatives < 2; alternatives++) {
+ // Now intersect this with the pkgFilter requested by the user, in order to
+ // only keep the classes that the user wants to install.
+ // We also create a set with the package indices requested by the user.
- String fqcn = basePackage + "." + name + "Package"; //$NON-NLS-1$ //$NON-NLS-2$
- try {
- Class<? extends Package> clazz =
- (Class<? extends Package>) classLoader.loadClass(fqcn);
- if (clazz != null) {
- pkgMap.put(node, clazz);
- continue;
- }
- } catch (Throwable ignore) {
- }
+ HashSet<Class<? extends Package>> userFilteredClasses =
+ new HashSet<Class<? extends Package>>();
+ SparseIntArray userFilteredIndices = new SparseIntArray();
- if (alternatives == 0 && dash > 0) {
- // Try an alternative where the next letter after the dash
- // is converted to an upper case.
- name = name.substring(0, dash) +
- name.substring(dash, dash + 1).toUpperCase() +
- name.substring(dash + 1);
- } else {
- break;
- }
- }
- }
+ for (String type : pkgFilter) {
+ if (type.replaceAll("[0-9]+", "").length() == 0) { //$NON-NLS-1$ //$NON-NLS-2$
+ // An all-digit number is a package index requested by the user.
+ int index = Integer.parseInt(type);
+ userFilteredIndices.put(index, index);
- if (SdkRepoConstants.NODES.length != pkgMap.size()) {
- // Sanity check in case we forget to update this node array.
- // We don't cancel the operation though.
- mSdkLog.printf(
- "*** Filter Mismatch! ***\n" +
- "*** The package filter list has changed. Please report this.");
- }
+ } else if (pkgMap.containsKey(type)) {
+ userFilteredClasses.add(pkgMap.get(type));
- // Now make a set of the types that are allowed by the filter.
- HashSet<Class<? extends Package>> allowedPkgSet =
- new HashSet<Class<? extends Package>>();
- for (String type : pkgFilter) {
- if (pkgMap.containsKey(type)) {
- allowedPkgSet.add(pkgMap.get(type));
} else {
// This should not happen unless there's a mismatch in the package map.
mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type);
@@ -811,15 +820,24 @@ class UpdaterData implements IUpdaterData { // we don't need the map anymore
pkgMap = null;
- Iterator<ArchiveInfo> it = archives.iterator();
- while (it.hasNext()) {
+ // Now filter the remote archives list to keep:
+ // - any package which class matches userFilteredClasses
+ // - any package index which matches userFilteredIndices
+
+ int index = 1;
+ for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
boolean keep = false;
ArchiveInfo ai = it.next();
Archive a = ai.getNewArchive();
if (a != null) {
Package p = a.getParentPackage();
- if (p != null && allowedPkgSet.contains(p.getClass())) {
- keep = true;
+ if (p != null) {
+ if (userFilteredClasses.contains(p.getClass()) ||
+ userFilteredIndices.get(index) > 0) {
+ keep = true;
+ }
+
+ index++;
}
}
@@ -856,6 +874,52 @@ class UpdaterData implements IUpdaterData { }
}
+ @SuppressWarnings("unchecked")
+ private void mapFilterToPackageClass(
+ HashMap<String, Class<? extends Package>> inOutPkgMap,
+ String[] nodes) {
+
+ // Automatically find the classes matching the node names
+ ClassLoader classLoader = getClass().getClassLoader();
+ String basePackage = Package.class.getPackage().getName();
+
+ for (String node : nodes) {
+ // Capitalize the name
+ String name = node.substring(0, 1).toUpperCase() + node.substring(1);
+
+ // We can have one dash at most in a name. If it's present, we'll try
+ // with the dash or with the next letter capitalized.
+ int dash = name.indexOf('-');
+ if (dash > 0) {
+ name = name.replaceFirst("-", "");
+ }
+
+ for (int alternatives = 0; alternatives < 2; alternatives++) {
+
+ String fqcn = basePackage + '.' + name + "Package"; //$NON-NLS-1$
+ try {
+ Class<? extends Package> clazz =
+ (Class<? extends Package>) classLoader.loadClass(fqcn);
+ if (clazz != null) {
+ inOutPkgMap.put(node, clazz);
+ continue;
+ }
+ } catch (Throwable ignore) {
+ }
+
+ if (alternatives == 0 && dash > 0) {
+ // Try an alternative where the next letter after the dash
+ // is converted to an upper case.
+ name = name.substring(0, dash) +
+ name.substring(dash, dash + 1).toUpperCase() +
+ name.substring(dash + 1);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
/**
* Refresh all sources. This is invoked either internally (reusing an existing monitor)
* or as a UI callback on the remote page "Refresh" button (in which case the monitor is
@@ -877,6 +941,7 @@ class UpdaterData implements IUpdaterData { }
SdkSource[] sources = mSources.getAllSources();
+ monitor.setDescription("Refresh Sources");
monitor.setProgressMax(monitor.getProgress() + sources.length);
for (SdkSource source : sources) {
if (forceFetching ||
@@ -919,19 +984,19 @@ class UpdaterData implements IUpdaterData { //
// Since SDK_TEST_URLS can contain many such URLs, we take the first one that
// matches our criteria.
- String url = System.getenv("SDK_TEST_URLS");
+ String url = System.getenv("SDK_TEST_URLS"); //$NON-NLS-1$
if (url == null) {
// No override, use the canonical URL.
url = SdkAddonsListConstants.URL_ADDON_LIST;
} else {
- String[] urls = url.split(";");
+ String[] urls = url.split(";"); //$NON-NLS-1$
url = null;
for (String u : urls) {
u = u.trim();
// This is an URL that comes from the env var. We expect it to either
// end with a / or the canonical name, otherwise we don't use it.
- if (u.endsWith("/")) {
+ if (u.endsWith("/")) { //$NON-NLS-1$
url = u + SdkAddonsListConstants.URL_DEFAULT_FILENAME;
break;
} else if (u.endsWith(SdkAddonsListConstants.URL_DEFAULT_FILENAME)) {
@@ -943,7 +1008,7 @@ class UpdaterData implements IUpdaterData { if (url != null) {
if (getSettingsController().getForceHttp()) {
- url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
+ url = url.replaceAll("https://", "http://"); //$NON-NLS-1$ //$NON-NLS-2$
}
AddonsListFetcher fetcher = new AddonsListFetcher();
@@ -959,6 +1024,8 @@ class UpdaterData implements IUpdaterData { mStateFetchRemoteAddonsList = 1;
}
}
+
+ monitor.setDescription("Fetched Add-ons List successfully");
}
/**
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java index 91afed6..c2472d8 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterLogic.java @@ -40,9 +40,12 @@ import com.android.sdklib.internal.repository.ToolPackage; import com.android.sdklib.internal.repository.Package.UpdateInfo;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* The logic to compute which packages to install, based on the choices
@@ -120,6 +123,20 @@ class UpdaterLogic { /**
* Finds new packages that the user does not have in his/her local SDK
* and adds them to the list of archives to install.
+ * <p/>
+ * The default is to only find "new" platforms, that is anything more
+ * recent than the highest platform currently installed.
+ * A side effect is that for an empty SDK install this will list *all*
+ * platforms available (since there's no "highest" installed platform.)
+ *
+ * @param archives The in-out list of archives to install. Typically the
+ * list is not empty at first as it should contain any archives that is
+ * already scheduled for install. This method will add to the list.
+ * @param sources The list of all sources, to fetch them as necessary.
+ * @param localPkgs The list of all currently installed packages.
+ * @param includeObsoletes When true, this will list all platform
+ * (included these lower than the highest installed one) as well as
+ * all obsolete packages of these platforms.
*/
public void addNewPlatforms(
Collection<ArchiveInfo> archives,
@@ -136,32 +153,34 @@ class UpdaterLogic { float currentAddonScore = 0;
float currentDocScore = 0;
HashMap<String, Float> currentExtraScore = new HashMap<String, Float>();
- if (localPkgs != null) {
- for (Package p : localPkgs) {
- int rev = p.getRevision();
- int api = 0;
- boolean isPreview = false;
- if (p instanceof IPackageVersion) {
- AndroidVersion vers = ((IPackageVersion) p).getVersion();
- api = vers.getApiLevel();
- isPreview = vers.isPreview();
- }
-
- // The score is 10*api + (1 if preview) + rev/100
- // This allows previews to rank above a non-preview and
- // allows revisions to rank appropriately.
- float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
+ if (!includeObsoletes) {
+ if (localPkgs != null) {
+ for (Package p : localPkgs) {
+ int rev = p.getRevision();
+ int api = 0;
+ boolean isPreview = false;
+ if (p instanceof IPackageVersion) {
+ AndroidVersion vers = ((IPackageVersion) p).getVersion();
+ api = vers.getApiLevel();
+ isPreview = vers.isPreview();
+ }
- if (p instanceof PlatformPackage) {
- currentPlatformScore = Math.max(currentPlatformScore, score);
- } else if (p instanceof SamplePackage) {
- currentSampleScore = Math.max(currentSampleScore, score);
- } else if (p instanceof AddonPackage) {
- currentAddonScore = Math.max(currentAddonScore, score);
- } else if (p instanceof ExtraPackage) {
- currentExtraScore.put(((ExtraPackage) p).getPath(), score);
- } else if (p instanceof DocPackage) {
- currentDocScore = Math.max(currentDocScore, score);
+ // The score is 10*api + (1 if preview) + rev/100
+ // This allows previews to rank above a non-preview and
+ // allows revisions to rank appropriately.
+ float score = api * 10 + (isPreview ? 1 : 0) + rev/100.f;
+
+ if (p instanceof PlatformPackage) {
+ currentPlatformScore = Math.max(currentPlatformScore, score);
+ } else if (p instanceof SamplePackage) {
+ currentSampleScore = Math.max(currentSampleScore, score);
+ } else if (p instanceof AddonPackage) {
+ currentAddonScore = Math.max(currentAddonScore, score);
+ } else if (p instanceof ExtraPackage) {
+ currentExtraScore.put(((ExtraPackage) p).getPath(), score);
+ } else if (p instanceof DocPackage) {
+ currentDocScore = Math.max(currentDocScore, score);
+ }
}
}
}
@@ -460,7 +479,7 @@ class UpdaterLogic { // - platform: *might* depends on tools of rev >= min-tools-rev
// - extra: *might* depends on platform with api >= min-api-level
- ArrayList<ArchiveInfo> list = new ArrayList<ArchiveInfo>();
+ Set<ArchiveInfo> aiFound = new HashSet<ArchiveInfo>();
if (pkg instanceof IPlatformDependency) {
ArchiveInfo ai = findPlatformDependency(
@@ -472,7 +491,7 @@ class UpdaterLogic { localArchives);
if (ai != null) {
- list.add(ai);
+ aiFound.add(ai);
}
}
@@ -487,7 +506,7 @@ class UpdaterLogic { localArchives);
if (ai != null) {
- list.add(ai);
+ aiFound.add(ai);
}
}
@@ -502,7 +521,7 @@ class UpdaterLogic { localArchives);
if (ai != null) {
- list.add(ai);
+ aiFound.add(ai);
}
}
@@ -517,7 +536,7 @@ class UpdaterLogic { localArchives);
if (ai != null) {
- list.add(ai);
+ aiFound.add(ai);
}
}
@@ -532,12 +551,14 @@ class UpdaterLogic { localArchives);
if (ai != null) {
- list.add(ai);
+ aiFound.add(ai);
}
}
- if (list.size() > 0) {
- return list.toArray(new ArchiveInfo[list.size()]);
+ if (aiFound.size() > 0) {
+ ArchiveInfo[] result = aiFound.toArray(new ArchiveInfo[aiFound.size()]);
+ Arrays.sort(result);
+ return result;
}
return null;
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java index 19d3916..7514dea 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl.java @@ -22,6 +22,7 @@ import com.android.sdklib.SdkConstants; import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.tasks.ProgressTaskFactory;
import com.android.sdkuilib.repository.ISdkChangeListener;
+import com.android.sdkuilib.repository.IUpdaterWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
@@ -31,7 +32,8 @@ import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
-import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.List;
@@ -41,9 +43,14 @@ import java.lang.reflect.Constructor; import java.util.ArrayList;
/**
- * This is the private implementation of the UpdateWindow.
+ * This is the private implementation of the UpdateWindow for the
+ * first version of the SDK Manager.
+ * <p/>
+ * This window has a sash, with a list of available pages on the left
+ * (AVD list, settings, about, installed packages, available packages)
+ * and the corresponding page on the right.
*/
-public class UpdaterWindowImpl {
+public class UpdaterWindowImpl implements IUpdaterWindow {
private final Shell mParentShell;
/** Internal data shared between the window and its pages. */
@@ -65,14 +72,13 @@ public class UpdaterWindowImpl { // --- UI members ---
- protected Shell mAndroidSdkUpdater;
- private SashForm mSashForm;
+ protected Shell mShell;
private List mPageList;
private Composite mPagesRootComposite;
- private LocalPackagesPage mLocalPackagePage;
- private RemotePackagesPage mRemotePackagesPage;
private AvdManagerPage mAvdManagerPage;
private StackLayout mStackLayout;
+ private LocalPackagesPage mLocalPackagePage;
+ private RemotePackagesPage mRemotePackagesPage;
/**
* Creates a new window. Caller must call open(), which will block.
@@ -87,7 +93,7 @@ public class UpdaterWindowImpl { }
/**
- * Open the window.
+ * Opens the window.
* @wbp.parser.entryPoint
*/
public void open() {
@@ -95,13 +101,15 @@ public class UpdaterWindowImpl { Display.setAppName("Android"); //$hide$ (hide from SWT designer)
}
+ createShell();
+ preCreateContent();
createContents();
- mAndroidSdkUpdater.open();
- mAndroidSdkUpdater.layout();
+ mShell.open();
+ mShell.layout();
- if (postCreate()) { //$hide$ (hide from SWT designer)
+ if (postCreateContent()) { //$hide$ (hide from SWT designer)
Display display = Display.getDefault();
- while (!mAndroidSdkUpdater.isDisposed()) {
+ while (!mShell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
@@ -111,27 +119,34 @@ public class UpdaterWindowImpl { dispose(); //$hide$
}
- /**
- * Create contents of the window.
- */
- protected void createContents() {
- mAndroidSdkUpdater = new Shell(mParentShell, SWT.SHELL_TRIM);
- mAndroidSdkUpdater.addDisposeListener(new DisposeListener() {
+ private void createShell() {
+ mShell = new Shell(mParentShell, SWT.SHELL_TRIM);
+ mShell.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer)
}
});
- FillLayout fl;
- mAndroidSdkUpdater.setLayout(fl = new FillLayout(SWT.HORIZONTAL));
- fl.marginHeight = fl.marginWidth = 5;
- mAndroidSdkUpdater.setMinimumSize(new Point(200, 50));
- mAndroidSdkUpdater.setSize(745, 433);
- mAndroidSdkUpdater.setText("Android SDK and AVD Manager");
+ GridLayout glShell = new GridLayout(2, false);
+ glShell.verticalSpacing = 0;
+ glShell.horizontalSpacing = 0;
+ glShell.marginWidth = 0;
+ glShell.marginHeight = 0;
+ mShell.setLayout(glShell);
+
+ mShell.setMinimumSize(new Point(500, 300));
+ mShell.setSize(700, 500);
+ mShell.setText("Android SDK and AVD Manager");
+ }
- mSashForm = new SashForm(mAndroidSdkUpdater, SWT.NONE);
+ /**
+ * Create contents of the window.
+ */
+ private void createContents() {
+ SashForm sashForm = new SashForm(mShell, SWT.NONE);
+ sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
- mPageList = new List(mSashForm, SWT.BORDER);
+ mPageList = new List(sashForm, SWT.BORDER);
mPageList.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
@@ -139,13 +154,15 @@ public class UpdaterWindowImpl { }
});
- mPagesRootComposite = new Composite(mSashForm, SWT.NONE);
- mStackLayout = new StackLayout();
- mPagesRootComposite.setLayout(mStackLayout);
+ createPagesRoot(sashForm);
- createPages();
+ sashForm.setWeights(new int[] {150, 576});
+ }
- mSashForm.setWeights(new int[] {150, 576});
+ private void createPagesRoot(Composite parent) {
+ mPagesRootComposite = new Composite(parent, SWT.NONE);
+ mStackLayout = new StackLayout();
+ mPagesRootComposite.setLayout(mStackLayout);
}
// -- Start of internal part ----------
@@ -166,7 +183,7 @@ public class UpdaterWindowImpl { * @param title The title of the page.
* @param pageClass The {@link Composite}-derived class that will implement the page.
*/
- public void registerExtraPage(String title, Class<? extends Composite> pageClass) {
+ public void registerPage(String title, Class<? extends Composite> pageClass) {
if (mExtraPages == null) {
mExtraPages = new ArrayList<Object[]>();
}
@@ -208,26 +225,22 @@ public class UpdaterWindowImpl { // --- Internals & UI Callbacks -----------
-
/**
- * Called by {@link #createContents()} to generate the pages that can be
+ * Called by {@link #postCreateContent()} to generate the pages that can be
* displayed in the window.
- * <p/>
- * Implementation detail: This is extracted from {@link #createContents()}
- * so that we can skip it when using WindowsBuilder, since {@link #mUpdaterData}
- * will then be null.
*/
- private void createPages() {
+ protected void createPages() {
mAvdManagerPage = new AvdManagerPage(mPagesRootComposite, mUpdaterData);
+
mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, mUpdaterData);
mRemotePackagesPage = new RemotePackagesPage(mPagesRootComposite, mUpdaterData);
- }
- /**
- * Helper to return the SWT shell.
- */
- private Shell getShell() {
- return mAndroidSdkUpdater;
+ addPage(mAvdManagerPage, "Virtual devices");
+
+ addPage(mLocalPackagePage, "Installed packages");
+ addPage(mRemotePackagesPage, "Available packages");
+
+ addExtraPages();
}
/**
@@ -254,44 +267,34 @@ public class UpdaterWindowImpl { if (mUpdaterData != null) {
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
- mAndroidSdkUpdater.setImage(imgFactory.getImageByName(imageName));
+ mShell.setImage(imgFactory.getImageByName(imageName));
}
}
}
/**
+ * Called before the UI is created.
+ */
+ private void preCreateContent() {
+ mUpdaterData.setWindowShell(mShell);
+ mTaskFactory = new ProgressTaskFactory(mShell);
+ mUpdaterData.setTaskFactory(mTaskFactory);
+ mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay()));
+ }
+
+ /**
* Once the UI has been created, initializes the content.
* This creates the pages, selects the first one, setup sources and scan for local folders.
*
* Returns true if we should show the window.
*/
- private boolean postCreate() {
- mUpdaterData.setWindowShell(getShell());
- mTaskFactory = new ProgressTaskFactory(getShell());
- mUpdaterData.setTaskFactory(mTaskFactory);
- mUpdaterData.setImageFactory(new ImageFactory(getShell().getDisplay()));
-
- setWindowImage(mAndroidSdkUpdater);
-
- addPage(mAvdManagerPage, "Virtual devices");
- addPage(mLocalPackagePage, "Installed packages");
- addPage(mRemotePackagesPage, "Available packages");
- addExtraPages();
-
- int pageIndex = 0;
- int i = 0;
- for (Composite p : mPages) {
- if (p.getClass().equals(mInitialPage)) {
- pageIndex = i;
- break;
- }
- i++;
- }
- displayPage(pageIndex);
- mPageList.setSelection(pageIndex);
+ private boolean postCreateContent() {
+ setWindowImage(mShell);
+ createPages();
setupSources();
initializeSettings();
+ selectInitialPage();
if (mUpdaterData.checkIfInitFailed()) {
return false;
@@ -323,10 +326,12 @@ public class UpdaterWindowImpl { * Each page is a {@link Composite}. The title of the page is stored in the
* {@link Composite#getData()} field.
*/
- private void addPage(Composite page, String title) {
+ protected void addPage(Composite page, String title) {
page.setData(title);
mPages.add(page);
- mPageList.add(title);
+ if (mPageList != null) {
+ mPageList.add(title);
+ }
}
/**
@@ -335,7 +340,7 @@ public class UpdaterWindowImpl { * to the page list.
*/
@SuppressWarnings("unchecked")
- private void addExtraPages() {
+ protected void addExtraPages() {
if (mExtraPages == null) {
return;
}
@@ -371,7 +376,7 @@ public class UpdaterWindowImpl { * If this is not an internal page change, displays the given page.
*/
private void onPageListSelected() {
- if (mInternalPageChange == false) {
+ if (mInternalPageChange == false && mPageList != null) {
int index = mPageList.getSelectionIndex();
if (index >= 0) {
displayPage(index);
@@ -390,11 +395,15 @@ public class UpdaterWindowImpl { mStackLayout.topControl = page;
mPagesRootComposite.layout(true);
- if (!mInternalPageChange) {
+ if (!mInternalPageChange && mPageList != null) {
mInternalPageChange = true;
mPageList.setSelection(index);
mInternalPageChange = false;
}
+
+ if (page instanceof IPageListener) {
+ ((IPageListener) page).onPageSelected();
+ }
}
}
@@ -403,7 +412,6 @@ public class UpdaterWindowImpl { */
private void setupSources() {
mUpdaterData.setupDefaultSources();
- mRemotePackagesPage.onSdkReload();
}
/**
@@ -427,6 +435,28 @@ public class UpdaterWindowImpl { }
}
+ /**
+ * Select and show the initial page.
+ * This will be either the page which class matches {@link #mInitialPage} or the
+ * first one in the list.
+ */
+ private void selectInitialPage() {
+ int pageIndex = 0;
+ int i = 0;
+ for (Composite p : mPages) {
+ if (p.getClass().equals(mInitialPage)) {
+ pageIndex = i;
+ break;
+ }
+ i++;
+ }
+
+ displayPage(pageIndex);
+ if (mPageList != null) {
+ mPageList.setSelection(pageIndex);
+ }
+ }
+
// End of hiding from SWT Designer
//$hide<<$
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl2.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl2.java new file mode 100755 index 0000000..6b3cd66 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/UpdaterWindowImpl2.java @@ -0,0 +1,586 @@ +/*
+ * 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.sdkuilib.internal.repository;
+
+
+import com.android.menubar.IMenuBarCallback;
+import com.android.menubar.MenuBarEnhancer;
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkConstants;
+import com.android.sdkuilib.internal.repository.PackagesPage.MenuAction;
+import com.android.sdkuilib.internal.repository.icons.ImageFactory;
+import com.android.sdkuilib.internal.tasks.ProgressView;
+import com.android.sdkuilib.internal.tasks.ProgressViewFactory;
+import com.android.sdkuilib.repository.ISdkChangeListener;
+import com.android.sdkuilib.repository.IUpdaterWindow;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.CLabel;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
+import org.eclipse.swt.events.MouseTrackListener;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.MenuItem;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.Shell;
+
+import java.util.ArrayList;
+
+/**
+ * This is the private implementation of the UpdateWindow
+ * for the second version of the SDK Manager.
+ * <p/>
+ * This window features only one embedded page, the combined installed+available package list.
+ */
+public class UpdaterWindowImpl2 implements IUpdaterWindow {
+
+ private static final String APP_NAME = "Android SDK Manager";
+ private final Shell mParentShell;
+ /** Internal data shared between the window and its pages. */
+ private final UpdaterData mUpdaterData;
+ /** A list of extra pages to instantiate. Each entry is an object array with 2 elements:
+ * the string title and the Composite class to instantiate to create the page. */
+ private ArrayList<Object[]> mExtraPages;
+ /** Sets whether the auto-update wizard will be shown when opening the window. */
+ private boolean mRequestAutoUpdate;
+
+ // --- UI members ---
+
+ protected Shell mShell;
+ private PackagesPage mPkgPage;
+ private ProgressBar mProgressBar;
+ private Label mStatusText;
+ private ImgDisabledButton mButtonStop;
+ private ToggleButton mButtonDetails;
+
+ /**
+ * Creates a new window. Caller must call open(), which will block.
+ *
+ * @param parentShell Parent shell.
+ * @param sdkLog Logger. Cannot be null.
+ * @param osSdkRoot The OS path to the SDK root.
+ */
+ public UpdaterWindowImpl2(Shell parentShell, ISdkLog sdkLog, String osSdkRoot) {
+ mParentShell = parentShell;
+ mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
+ }
+
+ /**
+ * Opens the window.
+ * @wbp.parser.entryPoint
+ */
+ public void open() {
+ if (mParentShell == null) {
+ Display.setAppName(APP_NAME); //$hide$ (hide from SWT designer)
+ }
+
+ createShell();
+ preCreateContent();
+ createContents();
+ createMenuBar();
+ mShell.open();
+ mShell.layout();
+
+ if (postCreateContent()) { //$hide$ (hide from SWT designer)
+ Display display = Display.getDefault();
+ while (!mShell.isDisposed()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+ }
+
+ dispose(); //$hide$
+ }
+
+ private void createShell() {
+ mShell = new Shell(mParentShell, SWT.SHELL_TRIM);
+ mShell.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer)
+ }
+ });
+
+ GridLayout glShell = new GridLayout(2, false);
+ glShell.verticalSpacing = 0;
+ glShell.horizontalSpacing = 0;
+ glShell.marginWidth = 0;
+ glShell.marginHeight = 0;
+ mShell.setLayout(glShell);
+
+ mShell.setMinimumSize(new Point(500, 300));
+ mShell.setSize(700, 500);
+ mShell.setText(APP_NAME);
+ }
+
+ private void createContents() {
+
+ mPkgPage = new PackagesPage(mShell, mUpdaterData);
+ mPkgPage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+
+ Composite composite1 = new Composite(mShell, SWT.NONE);
+ composite1.setLayout(new GridLayout(1, false));
+ composite1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ mProgressBar = new ProgressBar(composite1, SWT.NONE);
+ mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ mStatusText = new Label(composite1, SWT.NONE);
+ mStatusText.setText("Status Placeholder"); //$NON-NLS-1$ placeholder
+ mStatusText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ Composite composite2 = new Composite(mShell, SWT.NONE);
+ composite2.setLayout(new GridLayout(2, false));
+
+ mButtonStop = new ImgDisabledButton(composite2, SWT.NONE,
+ getImage("stop_enabled_16.png"), //$NON-NLS-1$
+ getImage("stop_disabled_16.png")); //$NON-NLS-1$
+ mButtonStop.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event event) {
+ onStopSelected();
+ }
+ });
+
+ mButtonDetails = new ToggleButton(composite2, SWT.NONE,
+ getImage("collapsed_16.png"), //$NON-NLS-1$
+ getImage("expanded_16.png")); //$NON-NLS-1$
+ mButtonDetails.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event event) {
+ onToggleDetails();
+ }
+ });
+ }
+
+ private void createMenuBar() {
+
+ Menu menuBar = new Menu(mShell, SWT.BAR);
+ mShell.setMenuBar(menuBar);
+
+ MenuItem menuBarPackages = new MenuItem(menuBar, SWT.CASCADE);
+ menuBarPackages.setText("Packages");
+
+ Menu menuPkgs = new Menu(menuBarPackages);
+ menuBarPackages.setMenu(menuPkgs);
+
+ MenuItem showUpdatesNew = new MenuItem(menuPkgs,
+ MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuStyle());
+ showUpdatesNew.setText(
+ MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG, showUpdatesNew);
+
+ MenuItem showInstalled = new MenuItem(menuPkgs,
+ MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuStyle());
+ showInstalled.setText(
+ MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.TOGGLE_SHOW_INSTALLED_PKG, showInstalled);
+
+ MenuItem showObsoletePackages = new MenuItem(menuPkgs,
+ MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuStyle());
+ showObsoletePackages.setText(
+ MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.TOGGLE_SHOW_OBSOLETE_PKG, showObsoletePackages);
+
+ MenuItem showArchives = new MenuItem(menuPkgs,
+ MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuStyle());
+ showArchives.setText(
+ MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.TOGGLE_SHOW_ARCHIVES, showArchives);
+
+ new MenuItem(menuPkgs, SWT.SEPARATOR);
+
+ MenuItem sortByApi = new MenuItem(menuPkgs,
+ MenuAction.SORT_API_LEVEL.getMenuStyle());
+ sortByApi.setText(
+ MenuAction.SORT_API_LEVEL.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.SORT_API_LEVEL, sortByApi);
+
+ MenuItem sortBySource = new MenuItem(menuPkgs,
+ MenuAction.SORT_SOURCE.getMenuStyle());
+ sortBySource.setText(
+ MenuAction.SORT_SOURCE.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.SORT_SOURCE, sortBySource);
+
+ new MenuItem(menuPkgs, SWT.SEPARATOR);
+
+ MenuItem reload = new MenuItem(menuPkgs,
+ MenuAction.RELOAD.getMenuStyle());
+ reload.setText(
+ MenuAction.RELOAD.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.RELOAD, reload);
+
+ MenuItem menuBarTools = new MenuItem(menuBar, SWT.CASCADE);
+ menuBarTools.setText("Tools");
+
+ Menu menuTools = new Menu(menuBarTools);
+ menuBarTools.setMenu(menuTools);
+
+ MenuItem manageAvds = new MenuItem(menuTools, SWT.NONE);
+ manageAvds.setText("Manage AVDs...");
+
+ MenuItem manageSources = new MenuItem(menuTools,
+ MenuAction.SHOW_ADDON_SITES.getMenuStyle());
+ manageSources.setText(
+ MenuAction.SHOW_ADDON_SITES.getMenuTitle());
+ mPkgPage.registerMenuAction(
+ MenuAction.SHOW_ADDON_SITES, manageSources);
+
+ MenuBarEnhancer.setupMenu(APP_NAME, menuTools, new IMenuBarCallback() {
+ public void onPreferencesMenuSelected() {
+ // TODO: plug settings page here
+ MessageDialog.openInformation(mShell, "test", "on prefs");
+ }
+
+ public void onAboutMenuSelected() {
+ // TODO: plug about page here
+ MessageDialog.openInformation(mShell, "test", "on about");
+ }
+
+ public void printError(String format, Object... args) {
+ if (mUpdaterData != null) {
+ // TODO: right now dump to stderr. Use sdklog later.
+ //mUpdaterData.getSdkLog().error(null, format, args);
+ System.err.printf(format, args);
+ }
+ }
+ });
+ }
+
+ private Image getImage(String filename) {
+ if (mUpdaterData != null) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (imgFactory != null) {
+ return imgFactory.getImageByName(filename);
+ }
+ }
+ return null;
+ }
+
+
+ // -- Start of internal part ----------
+ // Hide everything down-below from SWT designer
+ //$hide>>$
+
+ // --- Public API -----------
+
+
+ /**
+ * Registers an extra page for the updater window.
+ * <p/>
+ * Pages must derive from {@link Composite} and implement a constructor that takes
+ * a single parent {@link Composite} argument.
+ * <p/>
+ * All pages must be registered before the call to {@link #open()}.
+ *
+ * @param title The title of the page.
+ * @param pageClass The {@link Composite}-derived class that will implement the page.
+ */
+ public void registerPage(String title, Class<? extends Composite> pageClass) {
+ if (mExtraPages == null) {
+ mExtraPages = new ArrayList<Object[]>();
+ }
+ mExtraPages.add(new Object[]{ title, pageClass });
+ }
+
+ /**
+ * Indicate the initial page that should be selected when the window opens.
+ * This must be called before the call to {@link #open()}.
+ * If null or if the page class is not found, the first page will be selected.
+ */
+ public void setInitialPage(Class<? extends Composite> pageClass) {
+ // Unused in this case. This window display only one page.
+ }
+
+ /**
+ * Sets whether the auto-update wizard will be shown when opening the window.
+ * <p/>
+ * This must be called before the call to {@link #open()}.
+ */
+ public void setRequestAutoUpdate(boolean requestAutoUpdate) {
+ mRequestAutoUpdate = requestAutoUpdate;
+ }
+
+ /**
+ * Adds a new listener to be notified when a change is made to the content of the SDK.
+ */
+ public void addListener(ISdkChangeListener listener) {
+ mUpdaterData.addListeners(listener);
+ }
+
+ /**
+ * Removes a new listener to be notified anymore when a change is made to the content of
+ * the SDK.
+ */
+ public void removeListener(ISdkChangeListener listener) {
+ mUpdaterData.removeListener(listener);
+ }
+
+ // --- Internals & UI Callbacks -----------
+
+ /**
+ * Called before the UI is created.
+ */
+ private void preCreateContent() {
+ mUpdaterData.setWindowShell(mShell);
+ // We need the UI factory to create the UI
+ mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay()));
+ // Note: we can't create the TaskFactory yet because we need the UI
+ // to be created first, so this is done in postCreateContent().
+ }
+
+ /**
+ * Once the UI has been created, initializes the content.
+ * This creates the pages, selects the first one, setup sources and scan for local folders.
+ *
+ * Returns true if we should show the window.
+ */
+ private boolean postCreateContent() {
+ ProgressViewFactory factory = new ProgressViewFactory();
+ factory.setProgressView(new ProgressView(
+ mStatusText, mProgressBar, mButtonStop));
+ mUpdaterData.setTaskFactory(factory);
+
+ setWindowImage(mShell);
+
+ setupSources();
+ initializeSettings();
+
+ if (mUpdaterData.checkIfInitFailed()) {
+ return false;
+ }
+
+ mUpdaterData.broadcastOnSdkLoaded();
+
+ if (mRequestAutoUpdate) {
+ mUpdaterData.updateOrInstallAll_WithGUI(
+ null /*selectedArchives*/,
+ false /* includeObsoletes */);
+ }
+
+ // Tell the one page its the selected one
+ mPkgPage.onPageSelected();
+
+ return true;
+ }
+
+ /**
+ * Creates the icon of the window shell.
+ */
+ private void setWindowImage(Shell androidSdkUpdater) {
+ String imageName = "android_icon_16.png"; //$NON-NLS-1$
+ if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
+ imageName = "android_icon_128.png"; //$NON-NLS-1$
+ }
+
+ if (mUpdaterData != null) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (imgFactory != null) {
+ mShell.setImage(imgFactory.getImageByName(imageName));
+ }
+ }
+ }
+
+ /**
+ * Called by the main loop when the window has been disposed.
+ */
+ private void dispose() {
+ mUpdaterData.getSources().saveUserAddons(mUpdaterData.getSdkLog());
+ }
+
+ /**
+ * Callback called when the window shell is disposed.
+ */
+ private void onAndroidSdkUpdaterDispose() {
+ if (mUpdaterData != null) {
+ ImageFactory imgFactory = mUpdaterData.getImageFactory();
+ if (imgFactory != null) {
+ imgFactory.dispose();
+ }
+ }
+ }
+
+ /**
+ * Used to initialize the sources.
+ */
+ private void setupSources() {
+ mUpdaterData.setupDefaultSources();
+ }
+
+ /**
+ * Initializes settings.
+ * This must be called after addExtraPages(), which created a settings page.
+ * Iterate through all the pages to find the first (and supposedly unique) setting page,
+ * and use it to load and apply these settings.
+ */
+ private void initializeSettings() {
+ SettingsController c = mUpdaterData.getSettingsController();
+ c.loadSettings();
+ c.applySettings();
+
+ // TODO give access to a settings dialog somehow (+about dialog)
+ // TODO c.setSettingsPage(settingsPage);
+ }
+
+ private void onToggleDetails() {
+ mButtonDetails.setState(1 - mButtonDetails.getState());
+ }
+
+ private void onStopSelected() {
+ // TODO
+ }
+
+ // End of hiding from SWT Designer
+ //$hide<<$
+
+ // -----
+
+ /**
+ * A label that can display 2 images depending on its internal state.
+ * This acts as a button by firing the {@link SWT#Selection} listener.
+ */
+ private static class ToggleButton extends CLabel {
+ private Image[] mImage = new Image[2];
+ private boolean mMouseIn;
+ private int mState = 0;
+
+
+ public ToggleButton(Composite parent, int style, Image image1, Image image2) {
+ super(parent, style);
+ mImage[0] = image1;
+ mImage[1] = image2;
+ updateImage();
+
+ addMouseListener(new MouseListener() {
+ public void mouseDown(MouseEvent e) {
+ // pass
+ }
+
+ public void mouseUp(MouseEvent e) {
+ // We select on mouse-up, as it should be properly done since this is the
+ // only way a user can cancel a button click by moving out of the button.
+ if (mMouseIn && e.button == 1) {
+ notifyListeners(SWT.Selection, new Event());
+ }
+ }
+
+ public void mouseDoubleClick(MouseEvent e) {
+ if (mMouseIn && e.button == 1) {
+ notifyListeners(SWT.DefaultSelection, new Event());
+ }
+ }
+ });
+
+ addMouseTrackListener(new MouseTrackListener() {
+ public void mouseExit(MouseEvent e) {
+ if (mMouseIn) {
+ mMouseIn = false;
+ redraw();
+ }
+ }
+
+ public void mouseEnter(MouseEvent e) {
+ if (!mMouseIn) {
+ mMouseIn = true;
+ redraw();
+ }
+ }
+
+ public void mouseHover(MouseEvent e) {
+ // pass
+ }
+ });
+ }
+
+ @Override
+ public int getStyle() {
+ int style = super.getStyle();
+ if (mMouseIn) {
+ style |= SWT.SHADOW_IN;
+ }
+ return style;
+ }
+
+ /**
+ * Sets current state.
+ * @param state A value 0 or 1.
+ */
+ public void setState(int state) {
+ assert state == 0 || state == 1;
+ mState = state;
+ updateImage();
+ redraw();
+ }
+
+ /**
+ * Returns the current state
+ * @return Returns the current state, either 0 or 1.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ protected void updateImage() {
+ setImage(mImage[getState()]);
+ }
+ }
+
+ /**
+ * A label that can display 2 images depending on its enabled/disabled state.
+ * This acts as a button by firing the {@link SWT#Selection} listener.
+ */
+ private static class ImgDisabledButton extends ToggleButton {
+ public ImgDisabledButton(Composite parent, int style,
+ Image imageEnabled, Image imageDisabled) {
+ super(parent, style, imageEnabled, imageDisabled);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ updateImage();
+ redraw();
+ }
+
+ @Override
+ public void setState(int state) {
+ throw new UnsupportedOperationException(); // not available for this type of button
+ }
+
+ @Override
+ public int getState() {
+ return (isDisposed() || !isEnabled()) ? 1 : 0;
+ }
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java index 4a38f75..877ba37 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/ImageFactory.java @@ -94,6 +94,10 @@ public class ImageFactory { return null;
}
+ if (object instanceof Image) {
+ return (Image) object;
+ }
+
String clz = object.getClass().getSimpleName();
if (clz.endsWith(Package.class.getSimpleName())) {
String name = clz.replaceFirst(Package.class.getSimpleName(), "").toLowerCase() + //$NON-NLS-1$
@@ -122,6 +126,10 @@ public class ImageFactory { }
}
+ if (object instanceof String) {
+ return getImageByName((String) object);
+ }
+
return null;
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_128.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_128.png Binary files differindex acc124d..830c04b 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_128.png +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/android_icon_128.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/collapsed_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/collapsed_16.png Binary files differnew file mode 100755 index 0000000..5f20b86 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/collapsed_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/expanded_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/expanded_16.png Binary files differnew file mode 100755 index 0000000..6e13998 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/expanded_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/nopkg_icon16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/nopkg_icon_16.png Binary files differindex 147837f..147837f 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/nopkg_icon16.png +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/nopkg_icon_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_installed_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_installed_16.png Binary files differnew file mode 100755 index 0000000..78b7e5a --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_installed_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_new_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_new_16.png Binary files differnew file mode 100755 index 0000000..0976ad4 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_new_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_update_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_update_16.png Binary files differnew file mode 100755 index 0000000..e766251 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkg_update_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkgcat_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkgcat_16.png Binary files differnew file mode 100755 index 0000000..cd9b807 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkgcat_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkgcat_other_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkgcat_other_16.png Binary files differnew file mode 100755 index 0000000..395a240 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/pkgcat_other_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/status_ok_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/status_ok_16.png Binary files differnew file mode 100755 index 0000000..eeb0a6f --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/status_ok_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/stop_disabled_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/stop_disabled_16.png Binary files differnew file mode 100755 index 0000000..ae6da31 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/stop_disabled_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/stop_enabled_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/stop_enabled_16.png Binary files differnew file mode 100755 index 0000000..7ce1864 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/stop_enabled_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java new file mode 100755 index 0000000..4a7922d --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/IProgressUiProvider.java @@ -0,0 +1,93 @@ +/* + * 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.sdkuilib.internal.tasks; + +import com.android.sdklib.internal.repository.ITaskMonitor; + +import org.eclipse.swt.widgets.ProgressBar; + +/** + * Interface for a user interface that displays both a task status + * (e.g. via an {@link ITaskMonitor}) and the progress state of the + * task (e.g. via a progress bar.) + * <p/> + * See {@link ITaskMonitor} for details on how a monitor expects to + * be displayed. + */ +interface IProgressUiProvider { + + public abstract boolean isCancelRequested(); + + /** + * Sets the description in the current task dialog. + * This method can be invoked from a non-UI thread. + */ + public abstract void setDescription(String description); + + /** + * Logs a "normal" information line. + * This method can be invoked from a non-UI thread. + */ + public abstract void log(String log); + + /** + * Logs an "error" information line. + * This method can be invoked from a non-UI thread. + */ + public abstract void logError(String log); + + /** + * Logs a "verbose" information line, that is extra details which are typically + * not that useful for the end-user and might be hidden until explicitly shown. + * This method can be invoked from a non-UI thread. + */ + public abstract void logVerbose(String log); + + /** + * Sets the max value of the progress bar. + * This method can be invoked from a non-UI thread. + * + * @see ProgressBar#setMaximum(int) + */ + public abstract void setProgressMax(int max); + + /** + * Sets the current value of the progress bar. + * This method can be invoked from a non-UI thread. + */ + public abstract void setProgress(int value); + + /** + * Returns the current value of the progress bar, + * between 0 and up to {@link #setProgressMax(int)} - 1. + * This method can be invoked from a non-UI thread. + */ + public abstract int getProgress(); + + /** + * Display a yes/no question dialog box. + * + * This implementation allow this to be called from any thread, it + * makes sure the dialog is opened synchronously in the ui thread. + * + * @param title The title of the dialog box + * @param message The error message + * @return true if YES was clicked. + */ + public abstract boolean displayPrompt(String title, String message); + +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java index db2b781..42d5558 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTask.java @@ -19,23 +19,16 @@ package com.android.sdkuilib.internal.tasks; import com.android.sdklib.internal.repository.ITask;
import com.android.sdklib.internal.repository.ITaskMonitor;
-import org.eclipse.jface.dialogs.MessageDialog;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
/**
- * An {@link ITaskMonitor} that displays a {@link ProgressDialog}.
+ * An {@link ITaskMonitor} that displays a {@link ProgressTaskDialog}.
*/
-public final class ProgressTask implements ITaskMonitor {
+public final class ProgressTask extends TaskMonitorImpl {
- private static final double MAX_COUNT = 10000.0;
-
- private final ProgressDialog mDialog;
+ private final ProgressTaskDialog mDialog;
private boolean mAutomaticallyCloseOnTaskCompletion = true;
- private double mIncCoef = 0;
- private double mValue = 0;
/**
@@ -45,86 +38,16 @@ public final class ProgressTask implements ITaskMonitor { * This blocks till the thread ends.
*/
public ProgressTask(Shell parent, String title, ITask task) {
- mDialog = new ProgressDialog(parent, createTaskThread(title, task));
+ super(new ProgressTaskDialog(parent));
+ mDialog = (ProgressTaskDialog) getUiProvider();
mDialog.setText(title);
- mDialog.open();
- }
-
- /**
- * Sets the description in the current task dialog.
- * This method can be invoked from a non-UI thread.
- */
- public void setDescription(String descriptionFormat, Object...args) {
- mDialog.setDescription(descriptionFormat, args);
- }
-
- /**
- * Sets the description in the current task dialog.
- * This method can be invoked from a non-UI thread.
- */
- public void setResult(String resultFormat, Object...args) {
- mAutomaticallyCloseOnTaskCompletion = false;
- mDialog.setResult(resultFormat, args);
- }
-
- /**
- * Sets the max value of the progress bar.
- * This method can be invoked from a non-UI thread.
- *
- * Weird things will happen if setProgressMax is called multiple times
- * *after* {@link #incProgress(int)}: we don't try to adjust it on the
- * fly.
- *
- * @see ProgressBar#setMaximum(int)
- */
- public void setProgressMax(int max) {
- assert max > 0;
- // Always set the dialog's progress max to 10k since it only handles
- // integers and we want to have a better inner granularity. Instead
- // we use the max to compute a coefficient for inc deltas.
- mDialog.setProgressMax((int) MAX_COUNT);
- mIncCoef = max > 0 ? MAX_COUNT / max : 0;
- assert mIncCoef > 0;
- }
-
- /**
- * Increments the current value of the progress bar.
- *
- * This method can be invoked from a non-UI thread.
- */
- public void incProgress(int delta) {
- if (delta > 0 && mIncCoef > 0) {
- internalIncProgress(delta * mIncCoef);
- }
- }
-
- private void internalIncProgress(double realDelta) {
- mValue += realDelta;
- mDialog.setProgress((int)mValue);
- }
-
- /**
- * Returns the current value of the progress bar,
- * between 0 and up to {@link #setProgressMax(int)} - 1.
- *
- * This method can be invoked from a non-UI thread.
- */
- public int getProgress() {
- assert mIncCoef > 0;
- return mIncCoef > 0 ? (int)(mDialog.getProgress() / mIncCoef) : 0;
- }
-
- /**
- * Returns true if the "Cancel" button was selected.
- * It is up to the task thread to pool this and exit.
- */
- public boolean isCancelRequested() {
- return mDialog.isCancelRequested();
+ mDialog.open(createTaskThread(title, task));
}
/**
* Creates a thread to run the task. The thread has not been started yet.
* When the task completes, requests to close the dialog.
+ *
* @return A new thread that will run the task. The thread has not been started yet.
*/
private Thread createTaskThread(String title, final ITask task) {
@@ -145,122 +68,12 @@ public final class ProgressTask implements ITaskMonitor { }
/**
- * Display a yes/no question dialog box.
- *
- * This implementation allow this to be called from any thread, it
- * makes sure the dialog is opened synchronously in the ui thread.
- *
- * @param title The title of the dialog box
- * @param message The error message
- * @return true if YES was clicked.
+ * Sets the dialog to not auto-close since we want the user to see the error.
+ * {@inheritDoc}
*/
- public boolean displayPrompt(final String title, final String message) {
- final Shell shell = mDialog.getParent();
- Display display = shell.getDisplay();
-
- // we need to ask the user what he wants to do.
- final boolean[] result = new boolean[] { false };
- display.syncExec(new Runnable() {
- public void run() {
- result[0] = MessageDialog.openQuestion(shell, title, message);
- }
- });
- return result[0];
- }
-
- /**
- * Creates a sub-monitor that will use up to tickCount on the progress bar.
- * tickCount must be 1 or more.
- */
- public ITaskMonitor createSubMonitor(int tickCount) {
- assert mIncCoef > 0;
- assert tickCount > 0;
- return new SubTaskMonitor(this, null, mValue, tickCount * mIncCoef);
- }
-
- private interface ISubTaskMonitor extends ITaskMonitor {
- public void subIncProgress(double realDelta);
- }
-
- private static class SubTaskMonitor implements ISubTaskMonitor {
-
- private final ProgressTask mRoot;
- private final ISubTaskMonitor mParent;
- private final double mStart;
- private final double mSpan;
- private double mSubValue;
- private double mSubCoef;
-
- /**
- * Creates a new sub task monitor which will work for the given range [start, start+span]
- * in its parent.
- *
- * @param root The ProgressTask root
- * @param parent The immediate parent. Can be the null or another sub task monitor.
- * @param start The start value in the root's coordinates
- * @param span The span value in the root's coordinates
- */
- public SubTaskMonitor(ProgressTask root,
- ISubTaskMonitor parent,
- double start,
- double span) {
- mRoot = root;
- mParent = parent;
- mStart = start;
- mSpan = span;
- mSubValue = start;
- }
-
- public boolean isCancelRequested() {
- return mRoot.isCancelRequested();
- }
-
- public void setDescription(String descriptionFormat, Object... args) {
- mRoot.setDescription(descriptionFormat, args);
- }
-
- public void setResult(String resultFormat, Object... args) {
- mRoot.setResult(resultFormat, args);
- }
-
- public void setProgressMax(int max) {
- assert max > 0;
- mSubCoef = max > 0 ? mSpan / max : 0;
- assert mSubCoef > 0;
- }
-
- public int getProgress() {
- assert mSubCoef > 0;
- return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0;
- }
-
- public void incProgress(int delta) {
- if (delta > 0 && mSubCoef > 0) {
- subIncProgress(delta * mSubCoef);
- }
- }
-
- public void subIncProgress(double realDelta) {
- mSubValue += realDelta;
- if (mParent != null) {
- mParent.subIncProgress(realDelta);
- } else {
- mRoot.internalIncProgress(realDelta);
- }
- }
-
- public boolean displayPrompt(String title, String message) {
- return mRoot.displayPrompt(title, message);
- }
-
- public ITaskMonitor createSubMonitor(int tickCount) {
- assert mSubCoef > 0;
- assert tickCount > 0;
- return new SubTaskMonitor(mRoot,
- this,
- mSubValue,
- tickCount * mSubCoef);
- }
+ @Override
+ public void logError(String format, Object...args) {
+ mAutomaticallyCloseOnTaskCompletion = false;
+ super.logError(format, args);
}
-
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskDialog.java index ff79c68..a4236fa 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressTaskDialog.java @@ -19,9 +19,12 @@ package com.android.sdkuilib.internal.tasks; import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.ITaskMonitor;
+import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
@@ -34,18 +37,16 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
-import org.eclipse.swt.events.ShellAdapter;
-import org.eclipse.swt.events.ShellEvent;
/**
- * Implements a {@link ProgressDialog}, used by the {@link ProgressTask} class.
+ * Implements a {@link ProgressTaskDialog}, used by the {@link ProgressTask} class.
* This separates the dialog UI from the task logic.
*
* Note: this does not implement the {@link ITaskMonitor} interface to avoid confusing
* SWT Designer.
*/
-final class ProgressDialog extends Dialog {
+final class ProgressTaskDialog extends Dialog implements IProgressUiProvider {
/**
* Min Y location for dialog. Need to deal with the menu bar on mac os.
@@ -79,29 +80,27 @@ final class ProgressDialog extends Dialog { private ProgressBar mProgressBar;
private Button mCancelButton;
private Text mResultText;
- private final Thread mTaskThread;
/**
* Create the dialog.
* @param parent Parent container
- * @param taskThread The thread to run the task.
*/
- public ProgressDialog(Shell parent, Thread taskThread) {
+ public ProgressTaskDialog(Shell parent) {
super(parent, SWT.APPLICATION_MODAL);
- mTaskThread = taskThread;
}
/**
* Open the dialog and blocks till it gets closed
+ * @param taskThread The thread to run the task. Cannot be null.
*/
- public void open() {
+ public void open(Thread taskThread) {
createContents();
- positionShell(); //$hide$ (hide from SWT designer)
+ positionShell(); //$hide$ (hide from SWT designer)
mDialogShell.open();
mDialogShell.layout();
- startThread(); //$hide$ (hide from SWT designer)
+ startThread(taskThread); //$hide$ (hide from SWT designer)
Display display = getParent().getDisplay();
while (!mDialogShell.isDisposed() && mCancelMode != CancelMode.CLOSE_AUTO) {
@@ -273,41 +272,48 @@ final class ProgressDialog extends Dialog { * Sets the description in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
- public void setDescription(final String descriptionFormat, final Object...args) {
+ public void setDescription(final String description) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mLabel.isDisposed()) {
- mLabel.setText(String.format(descriptionFormat, args));
+ mLabel.setText(description);
}
}
});
}
/**
- * Sets the description in the current task dialog.
+ * Adds to the log in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
- public void setResult(final String resultFormat, final Object...args) {
+ public void log(final String info) {
if (!mDialogShell.isDisposed()) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mResultText.isDisposed()) {
mResultText.setVisible(true);
- String newText = String.format(resultFormat, args);
String lastText = mResultText.getText();
if (lastText != null &&
lastText.length() > 0 &&
- !lastText.endsWith("\n") &&
- !newText.startsWith("\n")) {
- mResultText.append("\n");
+ !lastText.endsWith("\n") && //$NON-NLS-1$
+ !info.startsWith("\n")) { //$NON-NLS-1$
+ mResultText.append("\n"); //$NON-NLS-1$
}
- mResultText.append(newText);
+ mResultText.append(info);
}
}
});
}
}
+ public void logError(String info) {
+ log(info);
+ }
+
+ public void logVerbose(String info) {
+ log(info);
+ }
+
/**
* Sets the max value of the progress bar.
* This method can be invoked from a non-UI thread.
@@ -364,12 +370,35 @@ final class ProgressDialog extends Dialog { }
/**
+ * Display a yes/no question dialog box.
+ *
+ * This implementation allow this to be called from any thread, it
+ * makes sure the dialog is opened synchronously in the ui thread.
+ *
+ * @param title The title of the dialog box
+ * @param message The error message
+ * @return true if YES was clicked.
+ */
+ public boolean displayPrompt(final String title, final String message) {
+ Display display = mDialogShell.getDisplay();
+
+ // we need to ask the user what he wants to do.
+ final boolean[] result = new boolean[] { false };
+ display.syncExec(new Runnable() {
+ public void run() {
+ result[0] = MessageDialog.openQuestion(mDialogShell, title, message);
+ }
+ });
+ return result[0];
+ }
+
+ /**
* Starts the thread that runs the task.
* This is deferred till the UI is created.
*/
- private void startThread() {
- if (mTaskThread != null) {
- mTaskThread.start();
+ private void startThread(Thread taskThread) {
+ if (taskThread != null) {
+ taskThread.start();
}
}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java new file mode 100755 index 0000000..9878c85 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressView.java @@ -0,0 +1,258 @@ +/*
+ * 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.sdkuilib.internal.tasks;
+
+import com.android.sdklib.internal.repository.ITask;
+import com.android.sdklib.internal.repository.ITaskMonitor;
+
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.ProgressBar;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Widget;
+
+
+/**
+ * Implements a "view" that uses an existing progress bar, status button and
+ * status text to display a {@link ITaskMonitor}.
+ */
+public final class ProgressView implements IProgressUiProvider {
+
+ private static enum State {
+ /** View created but there's no task running. Next state can only be ACTIVE. */
+ IDLE,
+ /** A task is currently running. Next state is either STOP_PENDING or IDLE. */
+ ACTIVE,
+ /** Stop button has been clicked. Waiting for thread to finish. Next state is IDLE. */
+ STOP_PENDING,
+ }
+
+ /** The current mode of operation of the dialog. */
+ private State mState = State.IDLE;
+
+ // UI fields
+ private final Label mLabel;
+ private final Control mStopButton;
+ private final ProgressBar mProgressBar;
+
+ /**
+ * Accumulated log text. This is intended to be displayed in a scrollable
+ * text area. The various methods that append to the log might not be called
+ * from the UI thread, so accesses should be synchronized on the builder.
+ */
+ private final StringBuilder mLogText = new StringBuilder();
+
+
+ /**
+ * Creates a new {@link ProgressView} object, a simple "holder" for the various
+ * widgets used to display and update a progress + status bar.
+ */
+ public ProgressView(Label label, ProgressBar progressBar, Control stopButton) {
+ mLabel = label;
+ mProgressBar = progressBar;
+ mProgressBar.setEnabled(false);
+
+ mStopButton = stopButton;
+ mStopButton.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event event) {
+ if (mState == State.ACTIVE) {
+ changeState(State.STOP_PENDING);
+ }
+ }
+ });
+ }
+
+ /**
+ * Starts the task and block till it's either finished or cancelled.
+ */
+ public void startTask(final String title, final ITask task) {
+ if (task != null) {
+ try {
+ mLabel.setText(title);
+ mProgressBar.setSelection(0);
+ mProgressBar.setEnabled(true);
+ changeState(ProgressView.State.ACTIVE);
+
+ Runnable r = new Runnable() {
+ public void run() {
+ task.run(new TaskMonitorImpl(ProgressView.this));
+ }
+ };
+
+ Thread t = new Thread(r, title);
+ t.start();
+
+ // Process the app's event loop whilst we wait for the thread to finish
+ Display display = mProgressBar.getDisplay();
+ while (!mProgressBar.isDisposed() && t.isAlive()) {
+ if (!display.readAndDispatch()) {
+ display.sleep();
+ }
+ }
+
+
+ } catch (Exception e) {
+ // TODO log
+
+ } finally {
+ changeState(ProgressView.State.IDLE);
+ mProgressBar.setSelection(0);
+ mProgressBar.setEnabled(false);
+ }
+ }
+ }
+
+ private void syncExec(final Widget widget, final Runnable runnable) {
+ if (widget != null && !widget.isDisposed()) {
+ widget.getDisplay().syncExec(runnable);
+ }
+ }
+
+ private void changeState(State state) {
+ if (mState != null ) {
+ mState = state;
+ }
+
+ syncExec(mStopButton, new Runnable() {
+ public void run() {
+ mStopButton.setEnabled(mState == State.ACTIVE);
+ }
+ });
+
+ }
+
+ // --- Implementation of ITaskUiProvider ---
+
+ public boolean isCancelRequested() {
+ return mState != State.ACTIVE;
+ }
+
+ /**
+ * Sets the description in the current task dialog.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void setDescription(final String description) {
+ syncExec(mLabel, new Runnable() {
+ public void run() {
+ mLabel.setText(description);
+ }
+ });
+ synchronized (mLogText) {
+ mLogText.append("** ").append(description);
+ }
+ }
+
+ /**
+ * Logs a "normal" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void log(String log) {
+ synchronized (mLogText) {
+ mLogText.append("=> ").append(log);
+ }
+ }
+
+ /**
+ * Logs an "error" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logError(String log) {
+ synchronized (mLogText) {
+ mLogText.append("=> ").append(log);
+ }
+ }
+
+ /**
+ * Logs a "verbose" information line, that is extra details which are typically
+ * not that useful for the end-user and might be hidden until explicitly shown.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logVerbose(String log) {
+ synchronized (mLogText) {
+ mLogText.append("=> ").append(log);
+ }
+ }
+
+ /**
+ * Sets the max value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ *
+ * @see ProgressBar#setMaximum(int)
+ */
+ public void setProgressMax(final int max) {
+ syncExec(mProgressBar, new Runnable() {
+ public void run() {
+ mProgressBar.setMaximum(max);
+ }
+ });
+ }
+
+ /**
+ * Sets the current value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void setProgress(final int value) {
+ syncExec(mProgressBar, new Runnable() {
+ public void run() {
+ mProgressBar.setSelection(value);
+ }
+ });
+ }
+
+ /**
+ * Returns the current value of the progress bar,
+ * between 0 and up to {@link #setProgressMax(int)} - 1.
+ * This method can be invoked from a non-UI thread.
+ */
+ public int getProgress() {
+ final int[] result = new int[] { 0 };
+
+ if (!mProgressBar.isDisposed()) {
+ mProgressBar.getDisplay().syncExec(new Runnable() {
+ public void run() {
+ if (!mProgressBar.isDisposed()) {
+ result[0] = mProgressBar.getSelection();
+ }
+ }
+ });
+ }
+
+ return result[0];
+ }
+
+ public boolean displayPrompt(final String title, final String message) {
+ final boolean[] result = new boolean[] { false };
+
+ if (!mProgressBar.isDisposed()) {
+ final Shell shell = mProgressBar.getShell();
+ Display display = shell.getDisplay();
+
+ display.syncExec(new Runnable() {
+ public void run() {
+ result[0] = MessageDialog.openQuestion(shell, title, message);
+ }
+ });
+ }
+
+ return result[0];
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressViewFactory.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressViewFactory.java new file mode 100755 index 0000000..448b478 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/ProgressViewFactory.java @@ -0,0 +1,41 @@ +/*
+ * Copyright (C) 2009 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.sdkuilib.internal.tasks;
+
+import com.android.sdklib.internal.repository.ITask;
+import com.android.sdklib.internal.repository.ITaskFactory;
+
+/**
+ * An {@link ITaskFactory} that creates a new {@link ProgressTask} dialog
+ * for each new task.
+ */
+public final class ProgressViewFactory implements ITaskFactory {
+
+ private ProgressView mProgressView;
+
+ public ProgressViewFactory() {
+ }
+
+ public void setProgressView(ProgressView progressView) {
+ mProgressView = progressView;
+ }
+
+ public void start(String title, ITask task) {
+ assert mProgressView != null;
+ mProgressView.startTask(title, task);
+ }
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java new file mode 100755 index 0000000..c95db47 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/tasks/TaskMonitorImpl.java @@ -0,0 +1,259 @@ +/*
+ * 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.sdkuilib.internal.tasks;
+
+import com.android.sdklib.internal.repository.ITaskMonitor;
+
+import org.eclipse.swt.widgets.ProgressBar;
+
+/**
+ * Internal class that implements the logic of an {@link ITaskMonitor}.
+ * It doesn't deal with any UI directly. Instead it delegates the UI to
+ * the provided {@link IProgressUiProvider}.
+ */
+class TaskMonitorImpl implements ITaskMonitor {
+
+ private static final double MAX_COUNT = 10000.0;
+
+ private interface ISubTaskMonitor extends ITaskMonitor {
+ public void subIncProgress(double realDelta);
+ }
+
+ private double mIncCoef = 0;
+ private double mValue = 0;
+ private final IProgressUiProvider mUi;
+
+ /**
+ * Constructs a new {@link TaskMonitorImpl} that relies on the given
+ * {@link IProgressUiProvider} to change the user interface.
+ * @param ui The {@link IProgressUiProvider}. Cannot be null.
+ */
+ public TaskMonitorImpl(IProgressUiProvider ui) {
+ mUi = ui;
+ }
+
+ /** Returns the {@link IProgressUiProvider} passed to the constructor. */
+ public IProgressUiProvider getUiProvider() {
+ return mUi;
+ }
+
+ /**
+ * Sets the description in the current task dialog.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void setDescription(String format, Object... args) {
+ final String text = String.format(format, args);
+ mUi.setDescription(text);
+ }
+
+ /**
+ * Logs a "normal" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void log(String format, Object... args) {
+ String text = String.format(format, args);
+ mUi.log(text);
+ }
+
+ /**
+ * Logs an "error" information line.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logError(String format, Object... args) {
+ String text = String.format(format, args);
+ mUi.logError(text);
+ }
+
+ /**
+ * Logs a "verbose" information line, that is extra details which are typically
+ * not that useful for the end-user and might be hidden until explicitly shown.
+ * This method can be invoked from a non-UI thread.
+ */
+ public void logVerbose(String format, Object... args) {
+ String text = String.format(format, args);
+ mUi.logVerbose(text);
+ }
+
+ /**
+ * Sets the max value of the progress bar.
+ * This method can be invoked from a non-UI thread.
+ *
+ * Weird things will happen if setProgressMax is called multiple times
+ * *after* {@link #incProgress(int)}: we don't try to adjust it on the
+ * fly.
+ *
+ * @see ProgressBar#setMaximum(int)
+ */
+ public void setProgressMax(int max) {
+ assert max > 0;
+ // Always set the dialog's progress max to 10k since it only handles
+ // integers and we want to have a better inner granularity. Instead
+ // we use the max to compute a coefficient for inc deltas.
+ mUi.setProgressMax((int) MAX_COUNT);
+ mIncCoef = max > 0 ? MAX_COUNT / max : 0;
+ assert mIncCoef > 0;
+ }
+
+ /**
+ * Increments the current value of the progress bar.
+ *
+ * This method can be invoked from a non-UI thread.
+ */
+ public void incProgress(int delta) {
+ if (delta > 0 && mIncCoef > 0) {
+ internalIncProgress(delta * mIncCoef);
+ }
+ }
+
+ private void internalIncProgress(double realDelta) {
+ mValue += realDelta;
+ mUi.setProgress((int)mValue);
+ }
+
+ /**
+ * Returns the current value of the progress bar,
+ * between 0 and up to {@link #setProgressMax(int)} - 1.
+ *
+ * This method can be invoked from a non-UI thread.
+ */
+ public int getProgress() {
+ // mIncCoef is 0 if setProgressMax hasn't been used yet.
+ return mIncCoef > 0 ? (int)(mUi.getProgress() / mIncCoef) : 0;
+ }
+
+ /**
+ * Returns true if the "Cancel" button was selected.
+ * It is up to the task thread to pool this and exit.
+ */
+ public boolean isCancelRequested() {
+ return mUi.isCancelRequested();
+ }
+
+ /**
+ * Display a yes/no question dialog box.
+ *
+ * This implementation allow this to be called from any thread, it
+ * makes sure the dialog is opened synchronously in the ui thread.
+ *
+ * @param title The title of the dialog box
+ * @param message The error message
+ * @return true if YES was clicked.
+ */
+ public boolean displayPrompt(final String title, final String message) {
+ return mUi.displayPrompt(title, message);
+ }
+
+ /**
+ * Creates a sub-monitor that will use up to tickCount on the progress bar.
+ * tickCount must be 1 or more.
+ */
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ assert mIncCoef > 0;
+ assert tickCount > 0;
+ return new SubTaskMonitor(this, null, mValue, tickCount * mIncCoef);
+ }
+
+ private static class SubTaskMonitor implements ISubTaskMonitor {
+
+ private final TaskMonitorImpl mRoot;
+ private final ISubTaskMonitor mParent;
+ private final double mStart;
+ private final double mSpan;
+ private double mSubValue;
+ private double mSubCoef;
+
+ /**
+ * Creates a new sub task monitor which will work for the given range [start, start+span]
+ * in its parent.
+ *
+ * @param taskMonitor The ProgressTask root
+ * @param parent The immediate parent. Can be the null or another sub task monitor.
+ * @param start The start value in the root's coordinates
+ * @param span The span value in the root's coordinates
+ */
+ public SubTaskMonitor(TaskMonitorImpl taskMonitor,
+ ISubTaskMonitor parent,
+ double start,
+ double span) {
+ mRoot = taskMonitor;
+ mParent = parent;
+ mStart = start;
+ mSpan = span;
+ mSubValue = start;
+ }
+
+ public boolean isCancelRequested() {
+ return mRoot.isCancelRequested();
+ }
+
+ public void setDescription(String format, Object... args) {
+ mRoot.setDescription(format, args);
+ }
+
+ public void log(String format, Object... args) {
+ mRoot.log(format, args);
+ }
+
+ public void logError(String format, Object... args) {
+ mRoot.logError(format, args);
+ }
+
+ public void logVerbose(String format, Object... args) {
+ mRoot.logVerbose(format, args);
+ }
+
+ public void setProgressMax(int max) {
+ assert max > 0;
+ mSubCoef = max > 0 ? mSpan / max : 0;
+ assert mSubCoef > 0;
+ }
+
+ public int getProgress() {
+ assert mSubCoef > 0;
+ return mSubCoef > 0 ? (int)((mSubValue - mStart) / mSubCoef) : 0;
+ }
+
+ public void incProgress(int delta) {
+ if (delta > 0 && mSubCoef > 0) {
+ subIncProgress(delta * mSubCoef);
+ }
+ }
+
+ public void subIncProgress(double realDelta) {
+ mSubValue += realDelta;
+ if (mParent != null) {
+ mParent.subIncProgress(realDelta);
+ } else {
+ mRoot.internalIncProgress(realDelta);
+ }
+ }
+
+ public boolean displayPrompt(String title, String message) {
+ return mRoot.displayPrompt(title, message);
+ }
+
+ public ITaskMonitor createSubMonitor(int tickCount) {
+ assert mSubCoef > 0;
+ assert tickCount > 0;
+ return new SubTaskMonitor(mRoot,
+ this,
+ mSubValue,
+ tickCount * mSubCoef);
+ }
+ }
+
+}
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java index 4411034..0932378 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java @@ -16,20 +16,21 @@ package com.android.sdkuilib.internal.widgets; -import com.android.prefs.AndroidLocation; +import com.android.io.FileWrapper; import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdklib.internal.avd.HardwareProperties; +import com.android.sdklib.internal.avd.AvdManager.AvdConflict; import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty; import com.android.sdklib.internal.project.ProjectProperties; -import com.android.sdklib.io.FileWrapper; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.ui.GridDialog; +import com.android.util.Pair; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.CellEditor; @@ -73,8 +74,8 @@ import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.TreeMap; +import java.util.Map.Entry; import java.util.regex.Matcher; /** @@ -97,11 +98,18 @@ final class AvdCreationDialog extends GridDialog { private final ArrayList<String> mEditedProperties = new ArrayList<String>(); private final ImageFactory mImageFactory; private final ISdkLog mSdkLog; + /** + * The original AvdInfo if we're editing an existing AVD. + * Null when we're creating a new AVD. + */ private final AvdInfo mEditAvdInfo; private Text mAvdName; private Combo mTargetCombo; + private Combo mAbiTypeCombo; + private String mAbiType; + private Button mSdCardSizeRadio; private Text mSdCardSize; private Combo mSdCardSizeCombo; @@ -153,8 +161,11 @@ final class AvdCreationDialog extends GridDialog { public void modifyText(ModifyEvent e) { String name = mAvdName.getText().trim(); if (mEditAvdInfo == null || !name.equals(mEditAvdInfo.getName())) { - AvdInfo avdMatch = mAvdManager.getAvd(name, false /*validAvdOnly*/); - if (avdMatch != null) { + // Case where we're creating a new AVD or editing an existing one + // and the AVD name has been changed... check for name uniqueness. + + Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(name); + if (conflict.getFirst() != AvdManager.AvdConflict.NO_CONFLICT) { // If we're changing the state from disabled to enabled, make sure // to uncheck the button, to force the user to voluntarily re-enforce it. // This happens when editing an existing AVD and changing the name from @@ -168,8 +179,10 @@ final class AvdCreationDialog extends GridDialog { mForceCreation.setSelection(false); } } else { + // Case where we're editing an existing AVD with the name unchanged. + mForceCreation.setEnabled(false); - mForceCreation.setSelection(true); + mForceCreation.setSelection(false); } validatePage(); } @@ -285,10 +298,29 @@ final class AvdCreationDialog extends GridDialog { public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); reloadSkinCombo(); + reloadAbiTypeCombo(); validatePage(); } }); + //ABI group + label = new Label(parent, SWT.NONE); + label.setText("ABI:"); + tooltip = "The ABI to use in the virtual device"; + label.setToolTipText(tooltip); + + mAbiTypeCombo = new Combo(parent, SWT.READ_ONLY | SWT.DROP_DOWN); + mAbiTypeCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mAbiTypeCombo.setToolTipText(tooltip); + mAbiTypeCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + validatePage(); + } + }); + mAbiTypeCombo.setEnabled(false); + // --- sd card group label = new Label(parent, SWT.NONE); label.setText("SD Card:"); @@ -322,6 +354,7 @@ final class AvdCreationDialog extends GridDialog { mSdCardSizeCombo = new Combo(sdCardGroup, SWT.DROP_DOWN | SWT.READ_ONLY); mSdCardSizeCombo.add("KiB"); mSdCardSizeCombo.add("MiB"); + mSdCardSizeCombo.add("GiB"); mSdCardSizeCombo.select(1); mSdCardSizeCombo.addSelectionListener(validateListener); @@ -668,80 +701,102 @@ final class AvdCreationDialog extends GridDialog { for (int i = 0;i < n; i++) { if (target.equals(mCurrentTargets.get(mTargetCombo.getItem(i)))) { mTargetCombo.select(i); + reloadAbiTypeCombo(); reloadSkinCombo(); break; } } } - Map<String, String> props = mEditAvdInfo.getProperties(); - - // First try the skin name and if it doesn't work fallback on the skin path - nextSkin: for (int s = 0; s < 2; s++) { - String skin = props.get(s == 0 ? AvdManager.AVD_INI_SKIN_NAME - : AvdManager.AVD_INI_SKIN_PATH); - if (skin != null && skin.length() > 0) { - Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin); - if (m.matches() && m.groupCount() == 2) { - enableSkinWidgets(false); - mSkinListRadio.setSelection(false); - mSkinSizeRadio.setSelection(true); - mSkinSizeWidth.setText(m.group(1)); - mSkinSizeHeight.setText(m.group(2)); - break nextSkin; - } else { - enableSkinWidgets(true); - mSkinSizeRadio.setSelection(false); - mSkinListRadio.setSelection(true); + // select the abi type + if (target != null && target.getAbiList().length > 0) { + mAbiTypeCombo.setEnabled(target.getAbiList().length > 1); + String abiType = AvdInfo.getPrettyAbiType(mEditAvdInfo.getAbiType()); + int n = mAbiTypeCombo.getItemCount(); + for (int i = 0; i < n; i++) { + if (abiType.equals(mAbiTypeCombo.getItem(i))) { + mAbiTypeCombo.select(i); + reloadSkinCombo(); + break; + } + } + } - int n = mSkinCombo.getItemCount(); - for (int i = 0; i < n; i++) { - if (skin.equals(mSkinCombo.getItem(i))) { - mSkinCombo.select(i); - break nextSkin; + Map<String, String> props = mEditAvdInfo.getProperties(); + if (props != null) { + // First try the skin name and if it doesn't work fallback on the skin path + nextSkin: for (int s = 0; s < 2; s++) { + String skin = props.get(s == 0 ? AvdManager.AVD_INI_SKIN_NAME + : AvdManager.AVD_INI_SKIN_PATH); + if (skin != null && skin.length() > 0) { + Matcher m = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin); + if (m.matches() && m.groupCount() == 2) { + enableSkinWidgets(false); + mSkinListRadio.setSelection(false); + mSkinSizeRadio.setSelection(true); + mSkinSizeWidth.setText(m.group(1)); + mSkinSizeHeight.setText(m.group(2)); + break nextSkin; + } else { + enableSkinWidgets(true); + mSkinSizeRadio.setSelection(false); + mSkinListRadio.setSelection(true); + + int n = mSkinCombo.getItemCount(); + for (int i = 0; i < n; i++) { + if (skin.equals(mSkinCombo.getItem(i))) { + mSkinCombo.select(i); + break nextSkin; + } } } } } - } - String sdcard = props.get(AvdManager.AVD_INI_SDCARD_PATH); - if (sdcard != null && sdcard.length() > 0) { - enableSdCardWidgets(false); - mSdCardSizeRadio.setSelection(false); - mSdCardFileRadio.setSelection(true); - mSdCardFile.setText(sdcard); - } + String sdcard = props.get(AvdManager.AVD_INI_SDCARD_PATH); + if (sdcard != null && sdcard.length() > 0) { + enableSdCardWidgets(false); + mSdCardSizeRadio.setSelection(false); + mSdCardFileRadio.setSelection(true); + mSdCardFile.setText(sdcard); + } + + sdcard = props.get(AvdManager.AVD_INI_SDCARD_SIZE); + if (sdcard != null && sdcard.length() > 0) { + String[] values = new String[2]; + long sdcardSize = AvdManager.parseSdcardSize(sdcard, values); + + if (sdcardSize != AvdManager.SDCARD_NOT_SIZE_PATTERN) { + enableSdCardWidgets(true); + mSdCardFileRadio.setSelection(false); + mSdCardSizeRadio.setSelection(true); + + mSdCardSize.setText(values[0]); - sdcard = props.get(AvdManager.AVD_INI_SDCARD_SIZE); - if (sdcard != null && sdcard.length() > 0) { - Matcher m = AvdManager.SDCARD_SIZE_PATTERN.matcher(sdcard); - if (m.matches() && m.groupCount() == 2) { - enableSdCardWidgets(true); - mSdCardFileRadio.setSelection(false); - mSdCardSizeRadio.setSelection(true); - - mSdCardSize.setText(m.group(1)); - - String suffix = m.group(2); - int n = mSdCardSizeCombo.getItemCount(); - for (int i = 0; i < n; i++) { - if (mSdCardSizeCombo.getItem(i).startsWith(suffix)) { - mSdCardSizeCombo.select(i); + String suffix = values[1]; + int n = mSdCardSizeCombo.getItemCount(); + for (int i = 0; i < n; i++) { + if (mSdCardSizeCombo.getItem(i).startsWith(suffix)) { + mSdCardSizeCombo.select(i); + } } } } - } - String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT); - if (snapshots != null && snapshots.length() > 0) { - mSnapshotCheck.setSelection(snapshots.equals("true")); + String snapshots = props.get(AvdManager.AVD_INI_SNAPSHOT_PRESENT); + if (snapshots != null && snapshots.length() > 0) { + mSnapshotCheck.setSelection(snapshots.equals("true")); + } } mProperties.clear(); - mProperties.putAll(props); + + if (props != null) { + mProperties.putAll(props); + } // Cleanup known non-hardware properties + mProperties.remove(AvdManager.AVD_INI_ABI_TYPE); mProperties.remove(AvdManager.AVD_INI_SKIN_PATH); mProperties.remove(AvdManager.AVD_INI_SKIN_NAME); mProperties.remove(AvdManager.AVD_INI_SDCARD_SIZE); @@ -882,6 +937,49 @@ final class AvdCreationDialog extends GridDialog { } /** + * Reload all the abi types in the selection list + */ + private void reloadAbiTypeCombo() { + String selected = null; + boolean found = false; + + int index = mTargetCombo.getSelectionIndex(); + if (index >= 0) { + String targetName = mTargetCombo.getItem(index); + IAndroidTarget target = mCurrentTargets.get(targetName); + String[] arches = target.getAbiList(); + + mAbiTypeCombo.setEnabled(arches.length > 1); + + // If user explicitly selected an ABI before, preserve that option + // If user did not explicitly select before (only one option before) + // force them to select + index = mAbiTypeCombo.getSelectionIndex(); + if (index >= 0 && mAbiTypeCombo.getItemCount() > 1) { + selected = mAbiTypeCombo.getItem(index); + } + + mAbiTypeCombo.removeAll(); + + int i; + for ( i = 0; i < arches.length ; i++ ) { + String prettyAbiType = AvdInfo.getPrettyAbiType(arches[i]); + mAbiTypeCombo.add(prettyAbiType); + if (!found) { + found = prettyAbiType.equals(selected); + if (found) { + mAbiTypeCombo.select(i); + } + } + } + + if (arches.length == 1) { + mAbiTypeCombo.select(0); + } + } + } + + /** * Validates the fields, displays errors and warnings. * Enables the finish button if there are no errors. */ @@ -905,6 +1003,16 @@ final class AvdCreationDialog extends GridDialog { error = "A target must be selected in order to create an AVD."; } + // validate abi type if the selected target supports multi archs. + if (hasAvdName && error == null && mTargetCombo.getSelectionIndex() > 0) { + int index = mTargetCombo.getSelectionIndex(); + String targetName = mTargetCombo.getItem(index); + IAndroidTarget target = mCurrentTargets.get(targetName); + if (target.getAbiList().length > 1 && mAbiTypeCombo.getSelectionIndex() < 0) { + error = "An abi type must be selected in order to create an AVD."; + } + } + // Validate SDCard path or value if (error == null) { // get the mode. We only need to check the file since the @@ -918,29 +1026,40 @@ final class AvdCreationDialog extends GridDialog { } else { String valueString = mSdCardSize.getText(); if (valueString.length() > 0) { - // prevent overflow: no more than 999GB - // 10 digit for MiB, 13 for KiB - if (valueString.length() >= 10 + - (mSdCardSizeCombo.getSelectionIndex() == 0 ? 3 : 0)) { - error = "SD Card size is too big!"; - } else { - try { - long value = Long.parseLong(valueString); - - switch (mSdCardSizeCombo.getSelectionIndex()) { - case 0: - value *= 1024L; - break; - case 1: - value *= 1024L * 1024L; - break; - } + long value = 0; + try { + value = Long.parseLong(valueString); + + int sizeIndex = mSdCardSizeCombo.getSelectionIndex(); + if (sizeIndex >= 0) { + // index 0 shifts by 10 (1024=K), index 1 by 20, etc. + value <<= 10*(1 + sizeIndex); + } - if (value < 9 * 1024 * 1024) { - error = "SD Card size must be at least 9 MiB"; + if (value < AvdManager.SDCARD_MIN_BYTE_SIZE || + value > AvdManager.SDCARD_MAX_BYTE_SIZE) { + value = 0; + } + } catch (Exception e) { + // ignore, we'll test value below. + } + if (value <= 0) { + error = "SD Card size is invalid. Range is 9 MiB..1023 GiB."; + } else if (mEditAvdInfo != null) { + // When editing an existing AVD, compare with the existing + // sdcard size, if any. It only matters if there was an sdcard setting + // before. + Map<String, String> props = mEditAvdInfo.getProperties(); + if (props != null) { + String original = + mEditAvdInfo.getProperties().get(AvdManager.AVD_INI_SDCARD_SIZE); + if (original != null && original.length() > 0) { + long originalSize = + AvdManager.parseSdcardSize(original, null/*parsedStrings*/); + if (originalSize > 0 && value != originalSize) { + warning = "A new SD Card file will be created.\nThe current SD Card file will be lost."; + } } - } catch (NumberFormatException e) { - // will never happen thanks to the VerifyListener. } } } @@ -957,24 +1076,47 @@ final class AvdCreationDialog extends GridDialog { String height = mSkinSizeHeight.getText(); // rejects non digit. if (width.length() == 0 || height.length() == 0) { - error = "Skin size is incorrect.\nBoth dimensions must be > 0"; + error = "Skin size is incorrect.\nBoth dimensions must be > 0."; } } } // Check for duplicate AVD name - if (isCreate && hasAvdName && error == null) { - AvdInfo avdMatch = mAvdManager.getAvd(avdName, false /*validAvdOnly*/); - if (avdMatch != null && !mForceCreation.getSelection()) { + if (isCreate && hasAvdName && error == null && !mForceCreation.getSelection()) { + Pair<AvdConflict, String> conflict = mAvdManager.isAvdNameConflicting(avdName); + assert conflict != null; + switch(conflict.getFirst()) { + case NO_CONFLICT: + break; + case CONFLICT_EXISTING_AVD: + case CONFLICT_INVALID_AVD: error = String.format( "The AVD name '%s' is already used.\n" + "Check \"Override the existing AVD\" to delete the existing one.", avdName); + break; + case CONFLICT_EXISTING_PATH: + error = String.format( + "Conflict with %s\n" + + "Check \"Override the existing AVD\" to delete the existing one.", + conflict.getSecond()); + break; + default: + // Hmm not supposed to happen... probably someone expanded the + // enum without adding something here. In this case just do an + // assert and use a generic error message. + error = String.format( + "Conflict %s with %s.\n" + + "Check \"Override the existing AVD\" to delete the existing one.", + conflict.getFirst().toString(), + conflict.getSecond()); + assert false; + break; } } if (error == null && mEditAvdInfo != null && isCreate) { - warning = String.format("The AVD '%1$s' will be duplicated into '%2$s'", + warning = String.format("The AVD '%1$s' will be duplicated into '%2$s'.", mEditAvdInfo.getName(), avdName); } @@ -1102,6 +1244,19 @@ final class AvdCreationDialog extends GridDialog { return false; } + // get the abi type + mAbiType = SdkConstants.ABI_ARMEABI; + if (target.getAbiList().length > 0) { + int abiIndex = mAbiTypeCombo.getSelectionIndex(); + if (abiIndex >= 0) { + String prettyname = mAbiTypeCombo.getItem(abiIndex); + //Extract the abi type + int firstIndex = prettyname.indexOf("("); + int lastIndex = prettyname.indexOf(")"); + mAbiType = prettyname.substring(firstIndex+1, lastIndex); + } + } + // get the SD card data from the UI. String sdName = null; if (mSdCardSizeRadio.getSelection()) { @@ -1112,10 +1267,13 @@ final class AvdCreationDialog extends GridDialog { // add the unit switch (mSdCardSizeCombo.getSelectionIndex()) { case 0: - sdName += "K"; + sdName += "K"; //$NON-NLS-1$ break; case 1: - sdName += "M"; + sdName += "M"; //$NON-NLS-1$ + break; + case 2: + sdName += "G"; //$NON-NLS-1$ break; default: // shouldn't be here @@ -1154,9 +1312,7 @@ final class AvdCreationDialog extends GridDialog { File avdFolder = null; try { - avdFolder = new File( - AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD, - avdName + AvdManager.AVD_FOLDER_EXTENSION); + avdFolder = AvdInfo.getDefaultAvdFolder(mAvdManager, avdName); } catch (AndroidLocationException e) { return false; } @@ -1169,11 +1325,13 @@ final class AvdCreationDialog extends GridDialog { avdFolder, avdName, target, + mAbiType, skinName, sdName, mProperties, - force, snapshot, + force, + mEditAvdInfo != null, //edit existing log); success = avdInfo != null; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdDetailsDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdDetailsDialog.java index 409c25d..6a85c14 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdDetailsDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdDetailsDialog.java @@ -18,9 +18,9 @@ package com.android.sdkuilib.internal.widgets; import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus; +import com.android.sdklib.internal.avd.AvdInfo.AvdStatus; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; @@ -101,7 +101,9 @@ final class AvdDetailsDialog extends Dialog { if (mAvdInfo != null) { displayValue(c, "Name:", mAvdInfo.getName()); - displayValue(c, "Path:", mAvdInfo.getPath()); + displayValue(c, "ABI:", AvdInfo.getPrettyAbiType(mAvdInfo.getAbiType())); + + displayValue(c, "Path:", mAvdInfo.getDataFolderPath()); if (mAvdInfo.getStatus() != AvdStatus.OK) { displayValue(c, "Error:", mAvdInfo.getErrorMessage()); @@ -135,6 +137,7 @@ final class AvdDetailsDialog extends Dialog { // display other hardware HashMap<String, String> copy = new HashMap<String, String>(properties); // remove stuff we already displayed (or that we don't want to display) + copy.remove(AvdManager.AVD_INI_ABI_TYPE); copy.remove(AvdManager.AVD_INI_SKIN_NAME); copy.remove(AvdManager.AVD_INI_SKIN_PATH); copy.remove(AvdManager.AVD_INI_SDCARD_SIZE); diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java index 5e26a41..56f2c7e 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java @@ -20,15 +20,15 @@ import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.ISdkLog; import com.android.sdklib.NullSdkLog; -import com.android.sdklib.SdkConstants; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus; +import com.android.sdklib.internal.avd.AvdInfo.AvdStatus; import com.android.sdklib.internal.repository.ITask; import com.android.sdklib.internal.repository.ITaskMonitor; import com.android.sdkuilib.internal.repository.SettingsController; import com.android.sdkuilib.internal.repository.icons.ImageFactory; import com.android.sdkuilib.internal.tasks.ProgressTask; +import com.android.sdkuilib.repository.IUpdaterWindow; import com.android.sdkuilib.repository.UpdaterWindow; import org.eclipse.jface.dialogs.MessageDialog; @@ -372,8 +372,10 @@ public final class AvdSelector { column2.setText("Platform"); final TableColumn column3 = new TableColumn(mTable, SWT.NONE); column3.setText("API Level"); + final TableColumn column4 = new TableColumn(mTable, SWT.NONE); + column4.setText("ABI"); - adjustColumnsWidth(mTable, column0, column1, column2, column3); + adjustColumnsWidth(mTable, column0, column1, column2, column3, column4); setupSelectionListener(mTable); fillTable(mTable); setEnabled(true); @@ -633,16 +635,18 @@ public final class AvdSelector { final TableColumn column0, final TableColumn column1, final TableColumn column2, - final TableColumn column3) { + final TableColumn column3, + final TableColumn column4) { // Add a listener to resize the column to the full width of the table table.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle r = table.getClientArea(); - column0.setWidth(r.width * 25 / 100); // 25% - column1.setWidth(r.width * 45 / 100); // 45% + column0.setWidth(r.width * 20 / 100); // 20% + column1.setWidth(r.width * 30 / 100); // 30% column2.setWidth(r.width * 15 / 100); // 15% column3.setWidth(r.width * 15 / 100); // 15% + column4.setWidth(r.width * 20 / 100); // 22% } }); } @@ -777,10 +781,12 @@ public final class AvdSelector { item.setText(1, target.getFullName()); item.setText(2, target.getVersionName()); item.setText(3, target.getVersion().getApiString()); + item.setText(4, AvdInfo.getPrettyAbiType(avd.getAbiType())); } else { item.setText(1, "?"); item.setText(2, "?"); item.setText(3, "?"); + item.setText(4, "?"); } } } @@ -1003,7 +1009,7 @@ public final class AvdSelector { log = new MessageBoxLog("Result of SDK Manager", display, true /*logErrorsOnly*/); } - UpdaterWindow window = new UpdaterWindow( + IUpdaterWindow window = new UpdaterWindow( mTable.getShell(), log, mAvdManager.getSdkManager().getLocation()); @@ -1025,10 +1031,7 @@ public final class AvdSelector { AvdStartDialog dialog = new AvdStartDialog(mTable.getShell(), avdInfo, mOsSdkPath, mController); if (dialog.open() == Window.OK) { - String path = mOsSdkPath + - File.separator + - SdkConstants.OS_SDK_TOOLS_FOLDER + - SdkConstants.FN_EMULATOR; + String path = avdInfo.getEmulatorPath(mOsSdkPath + File.separator); final String avdName = avdInfo.getName(); @@ -1070,7 +1073,8 @@ public final class AvdSelector { new ITask() { public void run(ITaskMonitor monitor) { try { - monitor.setDescription("Starting emulator for AVD '%1$s'", + monitor.setDescription( + "Starting emulator for AVD '%1$s'", avdName); int n = 10; monitor.setProgressMax(n); @@ -1092,7 +1096,7 @@ public final class AvdSelector { } } } catch (IOException e) { - monitor.setResult("Failed to start emulator: %1$s", + monitor.logError("Failed to start emulator: %1$s", e.getMessage()); } } @@ -1119,7 +1123,7 @@ public final class AvdSelector { while (true) { String line = errReader.readLine(); if (line != null) { - monitor.setResult("%1$s", line); //$NON-NLS-1$ + monitor.logError("%1$s", line); //$NON-NLS-1$ } else { break; } @@ -1140,7 +1144,7 @@ public final class AvdSelector { while (true) { String line = outReader.readLine(); if (line != null) { - monitor.setResult("%1$s", line); //$NON-NLS-1$ + monitor.log("%1$s", line); //$NON-NLS-1$ } else { break; } diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java index 77f47d1..7731dc1 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdStartDialog.java @@ -16,8 +16,8 @@ package com.android.sdkuilib.internal.widgets; +import com.android.sdklib.internal.avd.AvdInfo; import com.android.sdklib.internal.avd.AvdManager; -import com.android.sdklib.internal.avd.AvdManager.AvdInfo; import com.android.sdkuilib.internal.repository.SettingsController; import com.android.sdkuilib.ui.GridDialog; diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/MessageBoxLog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/MessageBoxLog.java index d5c1818..89edb2f 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/MessageBoxLog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/MessageBoxLog.java @@ -95,7 +95,15 @@ public final class MessageBoxLog implements ISdkLog { if (logMessages.size() > 0) { final StringBuilder sb = new StringBuilder(mMessage + "\n\n"); for (String msg : logMessages) { - sb.append(msg); + if (msg.length() > 0) { + if (msg.charAt(0) != '\n') { + int n = sb.length(); + if (n > 0 && sb.charAt(n-1) != '\n') { + sb.append('\n'); + } + } + sb.append(msg); + } } // display the message diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/IUpdaterWindow.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/IUpdaterWindow.java new file mode 100755 index 0000000..a771e53 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/IUpdaterWindow.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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.sdkuilib.repository; + +import org.eclipse.swt.widgets.Composite; + +public interface IUpdaterWindow { + + /** + * Registers an extra page for the updater window. + * <p/> + * Pages must derive from {@link Composite} and implement a constructor that takes + * a single parent {@link Composite} argument. + * <p/> + * All pages must be registered before the call to {@link #open()}. + * + * @param title The title of the page. + * @param pageClass The {@link Composite}-derived class that will implement the page. + */ + public abstract void registerPage(String title, + Class<? extends Composite> pageClass); + + /** + * Indicate the initial page that should be selected when the window opens. + * <p/> + * This must be called before the call to {@link #open()}. + * If null or if the page class is not found, the first page will be selected. + */ + public abstract void setInitialPage(Class<? extends Composite> pageClass); + + /** + * Sets whether the auto-update wizard will be shown when opening the window. + * <p/> + * This must be called before the call to {@link #open()}. + */ + public abstract void setRequestAutoUpdate(boolean requestAutoUpdate); + + /** + * Adds a new listener to be notified when a change is made to the content of the SDK. + */ + public abstract void addListener(ISdkChangeListener listener); + + /** + * Removes a new listener to be notified anymore when a change is made to the content of + * the SDK. + */ + public abstract void removeListener(ISdkChangeListener listener); + + /** + * Opens the window. + */ + public abstract void open(); + +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/UpdaterWindow.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/UpdaterWindow.java index c78b08c..d49b072 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/UpdaterWindow.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/repository/UpdaterWindow.java @@ -18,6 +18,7 @@ package com.android.sdkuilib.repository; import com.android.sdklib.ISdkLog;
import com.android.sdkuilib.internal.repository.UpdaterWindowImpl;
+import com.android.sdkuilib.internal.repository.UpdaterWindowImpl2;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
@@ -27,9 +28,9 @@ import org.eclipse.swt.widgets.Shell; *
* This is the public interface for using the window.
*/
-public class UpdaterWindow {
+public class UpdaterWindow implements IUpdaterWindow {
- private UpdaterWindowImpl mWindow;
+ private IUpdaterWindow mWindow;
/**
* Creates a new window. Caller must call open(), which will block.
@@ -39,7 +40,13 @@ public class UpdaterWindow { * @param osSdkRoot The OS path to the SDK root.
*/
public UpdaterWindow(Shell parentShell, ISdkLog sdkLog, String osSdkRoot) {
- mWindow = new UpdaterWindowImpl(parentShell, sdkLog, osSdkRoot);
+
+ // TODO right now the new PackagesPage is experimental and not enabled by default
+ if (System.getenv("ANDROID_SDKMAN_EXP") != null) { //$NON-NLS-1$
+ mWindow = new UpdaterWindowImpl2(parentShell, sdkLog, osSdkRoot);
+ } else {
+ mWindow = new UpdaterWindowImpl(parentShell, sdkLog, osSdkRoot);
+ }
}
/**
@@ -54,7 +61,7 @@ public class UpdaterWindow { * @param pageClass The {@link Composite}-derived class that will implement the page.
*/
public void registerPage(String title, Class<? extends Composite> pageClass) {
- mWindow.registerExtraPage(title, pageClass);
+ mWindow.registerPage(title, pageClass);
}
/**
diff --git a/anttasks/src/Android.mk b/sdkmanager/libs/sdkuilib/tests/Android.mk index 5cefb37..a7ba9a7 100644 --- a/anttasks/src/Android.mk +++ b/sdkmanager/libs/sdkuilib/tests/Android.mk @@ -1,5 +1,4 @@ -# -# Copyright (C) 2008 The Android Open Source Project +# 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. @@ -12,18 +11,22 @@ # 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-subdir-java-files) -LOCAL_JAR_MANIFEST := ../etc/manifest.txt -LOCAL_JAVA_LIBRARIES := \ - sdklib \ - ant +LOCAL_MODULE := sdkuilib-tests +LOCAL_MODULE_TAGS := optional -LOCAL_MODULE := anttasks +LOCAL_JAVA_LIBRARIES := \ + sdklib \ + sdklib-tests \ + sdkuilib \ + junit \ + swt \ include $(BUILD_HOST_JAVA_LIBRARY) - diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java index 185589f..9470f91 100755 --- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java +++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterDataTest.java @@ -31,7 +31,6 @@ import com.android.sdklib.mock.MockLog; import java.io.File; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.List; import java.util.Properties; @@ -187,7 +186,7 @@ public class UpdaterDataTest extends TestCase { return false; } - public void setDescription(String descriptionFormat, Object... args) { + public void setDescription(String format, Object... args) { // ignore } @@ -195,7 +194,15 @@ public class UpdaterDataTest extends TestCase { // ignore } - public void setResult(String resultFormat, Object... args) { + public void log(String format, Object... args) { + // ignore + } + + public void logError(String format, Object... args) { + // ignore + } + + public void logVerbose(String format, Object... args) { // ignore } } @@ -242,6 +249,11 @@ public class UpdaterDataTest extends TestCase { } @Override + public String getListDescription() { + return this.getClass().getSimpleName(); + } + + @Override public String getShortDescription() { return this.getClass().getSimpleName(); } diff --git a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java index 72229ff..84241d4 100755 --- a/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java +++ b/sdkmanager/libs/sdkuilib/tests/com/android/sdkuilib/internal/repository/UpdaterLogicTest.java @@ -21,16 +21,13 @@ import com.android.sdklib.SdkManager; import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.repository.Archive;
import com.android.sdklib.internal.repository.ITaskFactory;
-import com.android.sdklib.internal.repository.ITaskMonitor;
import com.android.sdklib.internal.repository.MockAddonPackage;
import com.android.sdklib.internal.repository.MockBrokenPackage;
import com.android.sdklib.internal.repository.MockPlatformPackage;
import com.android.sdklib.internal.repository.MockPlatformToolPackage;
import com.android.sdklib.internal.repository.MockToolPackage;
import com.android.sdklib.internal.repository.Package;
-import com.android.sdklib.internal.repository.SdkRepoSource;
import com.android.sdklib.internal.repository.SdkSource;
-import com.android.sdklib.internal.repository.SdkSourceCategory;
import com.android.sdklib.internal.repository.SdkSources;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
diff --git a/sdkstats/NOTICE b/sdkstats/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/sdkstats/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/swtmenubar/.classpath b/swtmenubar/.classpath new file mode 100644 index 0000000..0f69dc5 --- /dev/null +++ b/swtmenubar/.classpath @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/ANDROID_SWT"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/swtmenubar/.gitignore b/swtmenubar/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/swtmenubar/.gitignore @@ -0,0 +1 @@ +bin diff --git a/swtmenubar/.project b/swtmenubar/.project new file mode 100644 index 0000000..484282a --- /dev/null +++ b/swtmenubar/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>SwtMenuBar</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/sdkmanager/libs/sdklib/src/Android.mk b/swtmenubar/Android.mk index 37eda21..25e80da 100644 --- a/sdkmanager/libs/sdklib/src/Android.mk +++ b/swtmenubar/Android.mk @@ -1,11 +1,11 @@ # -# Copyright (C) 2008 The Android Open Source Project +# Copyright (C) 2011 The Android Open Source Project # -# Licensed under the Apache License, Version 2.0 (the "License"); +# 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.apache.org/licenses/LICENSE-2.0 +# 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, @@ -16,15 +16,21 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_JAVA_RESOURCE_DIRS := . -LOCAL_JAR_MANIFEST := ../manifest.txt +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_JAVA_RESOURCE_DIRS := src + +ifeq ($(HOST_OS),darwin) +LOCAL_SRC_FILES += $(call all-java-files-under, src-$(HOST_OS)) +LOCAL_JAVA_RESOURCE_DIRS += src-$(HOST_OS) +endif + +LOCAL_MODULE := swtmenubar +LOCAL_MODULE_TAGS := optional + LOCAL_JAVA_LIBRARIES := \ - androidprefs \ - common \ - commons-compress-1.0 + swt \ + org.eclipse.jface_3.4.2.M20090107-0800 -LOCAL_MODULE := sdklib include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/swtmenubar/MODULE_LICENSE_EPL b/swtmenubar/MODULE_LICENSE_EPL new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/swtmenubar/MODULE_LICENSE_EPL diff --git a/swtmenubar/NOTICE b/swtmenubar/NOTICE new file mode 100644 index 0000000..49c101d --- /dev/null +++ b/swtmenubar/NOTICE @@ -0,0 +1,224 @@ +*Eclipse Public License - v 1.0* + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE +PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF +THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +*1. DEFINITIONS* + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and +are distributed by that particular Contributor. A Contribution +'originates' from a Contributor if it was added to the Program by such +Contributor itself or anyone acting on such Contributor's behalf. +Contributions do not include additions to the Program which: (i) are +separate modules of software distributed in conjunction with the Program +under their own license agreement, and (ii) are not derivative works of +the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +*2. GRANT OF RIGHTS* + +a) Subject to the terms of this Agreement, each Contributor hereby +grants Recipient a non-exclusive, worldwide, royalty-free copyright +license to reproduce, prepare derivative works of, publicly display, +publicly perform, distribute and sublicense the Contribution of such +Contributor, if any, and such derivative works, in source code and +object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby +grants Recipient a non-exclusive, worldwide, royalty-free patent license +under Licensed Patents to make, use, sell, offer to sell, import and +otherwise transfer the Contribution of such Contributor, if any, in +source code and object code form. This patent license shall apply to the +combination of the Contribution and the Program if, at the time the +Contribution is added by the Contributor, such addition of the +Contribution causes such combination to be covered by the Licensed +Patents. The patent license shall not apply to any other combinations +which include the Contribution. No hardware per se is licensed hereunder. + +c) Recipient understands that although each Contributor grants the +licenses to its Contributions set forth herein, no assurances are +provided by any Contributor that the Program does not infringe the +patent or other intellectual property rights of any other entity. Each +Contributor disclaims any liability to Recipient for claims brought by +any other entity based on infringement of intellectual property rights +or otherwise. As a condition to exercising the rights and licenses +granted hereunder, each Recipient hereby assumes sole responsibility to +secure any other intellectual property rights needed, if any. For +example, if a third party patent license is required to allow Recipient +to distribute the Program, it is Recipient's responsibility to acquire +that license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright +license set forth in this Agreement. + +*3. REQUIREMENTS* + +A Contributor may choose to distribute the Program in object code form +under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties +and conditions, express and implied, including warranties or conditions +of title and non-infringement, and implied warranties or conditions of +merchantability and fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and +consequential damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are +offered by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable +manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained +within the Program. + +Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution. + +*4. COMMERCIAL DISTRIBUTION* + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, the +Contributor who includes the Program in a commercial product offering +should do so in a manner which does not create potential liability for +other Contributors. Therefore, if a Contributor includes the Program in +a commercial product offering, such Contributor ("Commercial +Contributor") hereby agrees to defend and indemnify every other +Contributor ("Indemnified Contributor") against any losses, damages and +costs (collectively "Losses") arising from claims, lawsuits and other +legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the +Program in a commercial product offering. The obligations in this +section do not apply to any claims or Losses relating to any actual or +alleged intellectual property infringement. In order to qualify, an +Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial +Contributor to control, and cooperate with the Commercial Contributor +in, the defense and any related settlement negotiations. The Indemnified +Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any other +Contributor to pay any damages as a result, the Commercial Contributor +must pay those damages. + +*5. NO WARRANTY* + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED +ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES +OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR +A PARTICULAR PURPOSE. Each Recipient is solely responsible for +determining the appropriateness of using and distributing the Program +and assumes all risks associated with its exercise of rights under this +Agreement , including but not limited to the risks and costs of program +errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +*6. DISCLAIMER OF LIABILITY* + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR +ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +*7. GENERAL* + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further action +by the parties hereto, such provision shall be reformed to the minimum +extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including +a cross-claim or counterclaim in a lawsuit) alleging that the Program +itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails +to comply with any of the material terms or conditions of this Agreement +and does not cure such failure in a reasonable period of time after +becoming aware of such noncompliance. If all Recipient's rights under +this Agreement terminate, Recipient agrees to cease use and distribution +of the Program as soon as reasonably practicable. However, Recipient's +obligations under this Agreement and any licenses granted by Recipient +relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and may +only be modified in the following manner. The Agreement Steward reserves +the right to publish new versions (including revisions) of this +Agreement from time to time. No one other than the Agreement Steward has +the right to modify this Agreement. The Eclipse Foundation is the +initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is +published, Contributor may elect to distribute the Program (including +its Contributions) under the new version. Except as expressly stated in +Sections 2(a) and 2(b) above, Recipient receives no rights or licenses +to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in +the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to +this Agreement will bring a legal action under this Agreement more than +one year after the cause of action arose. Each party waives its rights +to a jury trial in any resulting litigation. + + + diff --git a/swtmenubar/README b/swtmenubar/README new file mode 100755 index 0000000..ba7c25a --- /dev/null +++ b/swtmenubar/README @@ -0,0 +1,80 @@ +Using the Eclipse project SwtMenuBar +------------------------------------ + +This project provides a platform-specific way to hook into +the default OS menu bar. + +On MacOS, it allows an SWT app to have an About menu item +and to hook into the default Preferences menu item. + +On Windows and Linux, an SWT Menu should be provided (typically +named "Tools") into which the About and Options menu items +will be added. + + +Consequently the implementation contains platform-specific source +folders for the Java files that rely on a platform-specific version +of SWT.jar. + +Right now we have the following source folders: +- src/ - Generic implementation for all platforms. +- src-darwin/ - Implementation for MacOS Carbon. + +*Only* the default "src/" folder is declared in the project .classpath +so that the project can be opened in Eclipse on any platform and still +work. However that means that on MacOS the custom src-darwin folder is +not used by default. + + + +1- To build the library: + +Do not use Eclipse to build the library. Instead use the makefile: + +$ cd $TOP_OF_ANDROID_TREE +$ . build/envsetup.sh && lunch sdk-eng +$ make swtmenubar + +This will create a Jar in <Android tree>/out/host/<platform>/framework/ +that can then be included in the target application. + + +2- To use the library in a target application: + +Build the swtmenubar library as explained in step 1. + +In the target application, define a classpath variable in Eclipse: +- Open Preferences > Java > Build Path > Classpath Variables +- Create a new classpath variable named ANDROID_OUT_FRAMEWORK +- Set its folder value to <Android tree>/out/host/<platform>/framework + +Then add a variable to the Build Path of the target project: +- Open Project > Properties > Java Build Path +- Select the "Libraries" tab +- Use "Add Variable" +- Select ANDROID_OUT_FRAMEWORK +- Select "Extend..." +- Select swtmenubar.jar (which you previously built at step 1) + + +3- Tip for developing this library: + +Keep in mind that src-darwin folder must not be added to the +source folder list, otherwise the library would not compile +on Windows or Linux. + +If you change anything to IMenuBarCallback, make sure to test +on a Mac to be sure you're not breaking the API. + +To work on this on a Mac, you can either: +a- simply temporarily add src-darwin as a source folder to the + build path and remove it before submitting. +b- or directly edit the java files and rebuild the library using + 'make swtmenubar' from a shell. + +To test the library, use 'make swtmenubar'. This will build the +library in out/... and the sdkmanager project is already setup +to find it there. + +-- +EOF diff --git a/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCarbon.java b/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCarbon.java new file mode 100755 index 0000000..45dacfb --- /dev/null +++ b/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCarbon.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2011 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 com.android.menubar.internal; + +import com.android.menubar.IMenuBarCallback; +import com.android.menubar.IMenuBarEnhancer; + +import org.eclipse.swt.internal.Callback; +import org.eclipse.swt.internal.carbon.HICommand; +import org.eclipse.swt.internal.carbon.OS; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; + + +/** + * Implementation of IMenuBarEnhancer for MacOS Carbon SWT. + */ +public final class MenuBarEnhancerCarbon implements IMenuBarEnhancer { + + private static final int kHICommandPreferences = ('p'<<24) + ('r'<<16) + ('e'<<8) + 'f'; + private static final int kHICommandAbout = ('a'<<24) + ('b'<<16) + ('o'<<8) + 'u'; + private static final int kHICommandServices = ('s'<<24) + ('e'<<16) + ('r'<<8) + 'v'; + + public MenuBarEnhancerCarbon() { + } + + public MenuBarMode getMenuBarMode() { + return MenuBarMode.MAC_OS; + } + + public void setupMenu( + String appName, + Display display, + final IMenuBarCallback callbacks) { + + // Callback target + Object target = new Object() { + @SuppressWarnings("unused") + int commandProc(int nextHandler, int theEvent, int userData) { + if (OS.GetEventKind(theEvent) == OS.kEventProcessCommand) { + HICommand command = new HICommand(); + OS.GetEventParameter( + theEvent, + OS.kEventParamDirectObject, + OS.typeHICommand, + null, + HICommand.sizeof, + null, + command); + switch (command.commandID) { + case kHICommandPreferences: + callbacks.onPreferencesMenuSelected(); + return OS.eventNotHandledErr; // TODO wrong + case kHICommandAbout: + callbacks.onAboutMenuSelected(); + return OS.eventNotHandledErr;// TODO wrong + default: + break; + } + } + return OS.eventNotHandledErr; + } + }; + + final Callback commandCallback= new Callback(target, "commandProc", 3); //$NON-NLS-1$ + int commandProc = commandCallback.getAddress(); + if (commandProc == 0) { + commandCallback.dispose(); + log(callbacks, "%1$s: commandProc hook failed.", getClass().getSimpleName()); //$NON-NLS-1$ + return; // give up + } + + // Install event handler for commands + int[] mask = new int[] { + OS.kEventClassCommand, OS.kEventProcessCommand + }; + OS.InstallEventHandler( + OS.GetApplicationEventTarget(), commandProc, mask.length / 2, mask, 0, null); + + // create About Eclipse menu command + int[] outMenu = new int[1]; + short[] outIndex = new short[1]; + if (OS.GetIndMenuItemWithCommandID( + 0, kHICommandPreferences, 1, outMenu, outIndex) == OS.noErr && outMenu[0] != 0) { + int menu = outMenu[0]; + + // add About menu item (which isn't present by default) + String about = "About " + appName; + int l = about.length(); + char buffer[] = new char[l]; + about.getChars(0, l, buffer, 0); + int str = OS.CFStringCreateWithCharacters(OS.kCFAllocatorDefault, buffer, l); + OS.InsertMenuItemTextWithCFString(menu, str, (short) 0, 0, kHICommandAbout); + OS.CFRelease(str); + + // add separator between About & Preferences + OS.InsertMenuItemTextWithCFString(menu, 0, (short) 1, OS.kMenuItemAttrSeparator, 0); + + // enable pref menu + OS.EnableMenuCommand(menu, kHICommandPreferences); + + // disable services menu + OS.DisableMenuCommand(menu, kHICommandServices); + } + + // schedule disposal of callback object + display.disposeExec( + new Runnable() { + public void run() { + commandCallback.dispose(); + } + } + ); + } + + private void log(IMenuBarCallback callbacks, String format, Object... args) { + callbacks.printError(format , args); + } + +} diff --git a/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCocoa.java b/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCocoa.java new file mode 100644 index 0000000..170603a --- /dev/null +++ b/swtmenubar/src-darwin/com/android/menubar/internal/MenuBarEnhancerCocoa.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2011 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. + * + * History: + * Original code by the <a href="http://www.simidude.com/blog/2008/macify-a-swt-application-in-a-cross-platform-way/">CarbonUIEnhancer from Agynami</a> + * with the implementation being modified from the <a href="http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.ui.cocoa/src/org/eclipse/ui/internal/cocoa/CocoaUIEnhancer.java">org.eclipse.ui.internal.cocoa.CocoaUIEnhancer</a>, + * then modified by http://www.transparentech.com/opensource/cocoauienhancer to use reflection + * rather than 'link' to SWT cocoa, and finally modified to be usable by the SwtMenuBar project. + */ + +package com.android.menubar.internal; + +import com.android.menubar.IMenuBarCallback; +import com.android.menubar.IMenuBarEnhancer; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.internal.C; +import org.eclipse.swt.internal.Callback; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class MenuBarEnhancerCocoa implements IMenuBarEnhancer { + + private static final long kAboutMenuItem = 0; + private static final long kPreferencesMenuItem = 2; + // private static final long kServicesMenuItem = 4; + // private static final long kHideApplicationMenuItem = 6; + private static final long kQuitMenuItem = 10; + + static long mSelPreferencesMenuItemSelected; + static long mSelAboutMenuItemSelected; + static Callback mProc3Args; + + private String mAppName; + + /** + * Class invoked via the Callback object to run the about and preferences + * actions. + * <p> + * If you don't use JFace in your application (SWT only), change the + * {@link org.eclipse.jface.action.IAction}s to + * {@link org.eclipse.swt.widgets.Listener}s. + * </p> + */ + private static class ActionProctarget { + private final IMenuBarCallback mCallbacks; + + public ActionProctarget(IMenuBarCallback callbacks) { + mCallbacks = callbacks; + } + + /** + * Will be called on 32bit SWT. + */ + @SuppressWarnings("unused") + public int actionProc(int id, int sel, int arg0) { + return (int) actionProc((long) id, (long) sel, (long) arg0); + } + + /** + * Will be called on 64bit SWT. + */ + public long actionProc(long id, long sel, long arg0) { + if (sel == mSelAboutMenuItemSelected) { + mCallbacks.onAboutMenuSelected(); + } else if (sel == mSelPreferencesMenuItemSelected) { + mCallbacks.onPreferencesMenuSelected(); + } else { + // Unknown selection! + } + // Return value is not used. + return 0; + } + } + + /** + * Construct a new CocoaUIEnhancer. + * + * @param mAppName The name of the application. It will be used to customize + * the About and Quit menu items. If you do not wish to customize + * the About and Quit menu items, just pass <tt>null</tt> here. + */ + public MenuBarEnhancerCocoa() { + } + + public MenuBarMode getMenuBarMode() { + return MenuBarMode.MAC_OS; + } + + /** + * Setup the About and Preferences native menut items with the + * given application name and links them to the callback. + * + * @param appName The application name. + * @param display The SWT display. Must not be null. + * @param callbacks The callbacks invoked by the menus. + */ + public void setupMenu( + String appName, + Display display, + IMenuBarCallback callbacks) { + + mAppName = appName; + + // This is our callback object whose 'actionProc' method will be called + // when the About or Preferences menuItem is invoked. + ActionProctarget target = new ActionProctarget(callbacks); + + try { + // Initialize the menuItems. + initialize(target); + } catch (Exception e) { + throw new IllegalStateException(e); + } + + // Schedule disposal of callback object + display.disposeExec(new Runnable() { + public void run() { + invoke(mProc3Args, "dispose"); + } + }); + } + + private void initialize(Object callbackObject) + throws Exception { + + Class<?> osCls = classForName("org.eclipse.swt.internal.cocoa.OS"); + + // Register names in objective-c. + if (mSelAboutMenuItemSelected == 0) { + mSelPreferencesMenuItemSelected = registerName(osCls, "preferencesMenuItemSelected:"); //$NON-NLS-1$ + mSelAboutMenuItemSelected = registerName(osCls, "aboutMenuItemSelected:"); //$NON-NLS-1$ + } + + // Create an SWT Callback object that will invoke the actionProc method + // of our internal callback Object. + mProc3Args = new Callback(callbackObject, "actionProc", 3); //$NON-NLS-1$ + Method getAddress = Callback.class.getMethod("getAddress", new Class[0]); + Object object = getAddress.invoke(mProc3Args, (Object[]) null); + long proc3 = convertToLong(object); + if (proc3 == 0) { + SWT.error(SWT.ERROR_NO_MORE_CALLBACKS); + } + + Class<?> nsMenuCls = classForName("org.eclipse.swt.internal.cocoa.NSMenu"); + Class<?> nsMenuitemCls = classForName("org.eclipse.swt.internal.cocoa.NSMenuItem"); + Class<?> nsStringCls = classForName("org.eclipse.swt.internal.cocoa.NSString"); + Class<?> nsApplicationCls = classForName("org.eclipse.swt.internal.cocoa.NSApplication"); + + // Instead of creating a new delegate class in objective-c, + // just use the current SWTApplicationDelegate. An instance of this + // is a field of the Cocoa Display object and is already the target + // for the menuItems. So just get this class and add the new methods + // to it. + object = invoke(osCls, "objc_lookUpClass", new Object[] { + "SWTApplicationDelegate" + }); + long cls = convertToLong(object); + + // Add the action callbacks for Preferences and About menu items. + invoke(osCls, "class_addMethod", + new Object[] { + wrapPointer(cls), + wrapPointer(mSelPreferencesMenuItemSelected), + wrapPointer(proc3), "@:@"}); //$NON-NLS-1$ + invoke(osCls, "class_addMethod", + new Object[] { + wrapPointer(cls), + wrapPointer(mSelAboutMenuItemSelected), + wrapPointer(proc3), "@:@"}); //$NON-NLS-1$ + + // Get the Mac OS X Application menu. + Object sharedApplication = invoke(nsApplicationCls, "sharedApplication"); + Object mainMenu = invoke(sharedApplication, "mainMenu"); + Object mainMenuItem = invoke(nsMenuCls, mainMenu, "itemAtIndex", new Object[] { + wrapPointer(0) + }); + Object appMenu = invoke(mainMenuItem, "submenu"); + + // Create the About <application-name> menu command + Object aboutMenuItem = + invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { + wrapPointer(kAboutMenuItem) + }); + if (mAppName != null) { + Object nsStr = invoke(nsStringCls, "stringWith", new Object[] { + "About " + mAppName + }); + invoke(nsMenuitemCls, aboutMenuItem, "setTitle", new Object[] { + nsStr + }); + } + // Rename the quit action. + if (mAppName != null) { + Object quitMenuItem = + invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { + wrapPointer(kQuitMenuItem) + }); + Object nsStr = invoke(nsStringCls, "stringWith", new Object[] { + "Quit " + mAppName + }); + invoke(nsMenuitemCls, quitMenuItem, "setTitle", new Object[] { + nsStr + }); + } + + // Enable the Preferences menuItem. + Object prefMenuItem = + invoke(nsMenuCls, appMenu, "itemAtIndex", new Object[] { + wrapPointer(kPreferencesMenuItem) + }); + invoke(nsMenuitemCls, prefMenuItem, "setEnabled", new Object[] { + true + }); + + // Set the action to execute when the About or Preferences menuItem is + // invoked. + // + // We don't need to set the target here as the current target is the + // SWTApplicationDelegate and we have registerd the new selectors on + // it. So just set the new action to invoke the selector. + invoke(nsMenuitemCls, prefMenuItem, "setAction", + new Object[] { + wrapPointer(mSelPreferencesMenuItemSelected) + }); + invoke(nsMenuitemCls, aboutMenuItem, "setAction", + new Object[] { + wrapPointer(mSelAboutMenuItemSelected) + }); + } + + private long registerName(Class<?> osCls, String name) + throws IllegalArgumentException, SecurityException, IllegalAccessException, + InvocationTargetException, NoSuchMethodException { + Object object = invoke(osCls, "sel_registerName", new Object[] { + name + }); + return convertToLong(object); + } + + private long convertToLong(Object object) { + if (object instanceof Integer) { + Integer i = (Integer) object; + return i.longValue(); + } + if (object instanceof Long) { + Long l = (Long) object; + return l.longValue(); + } + return 0; + } + + private static Object wrapPointer(long value) { + Class<?> PTR_CLASS = C.PTR_SIZEOF == 8 ? long.class : int.class; + if (PTR_CLASS == long.class) { + return new Long(value); + } else { + return new Integer((int) value); + } + } + + private static Object invoke(Class<?> clazz, String methodName, Object[] args) { + return invoke(clazz, null, methodName, args); + } + + private static Object invoke(Class<?> clazz, Object target, String methodName, Object[] args) { + try { + Class<?>[] signature = new Class<?>[args.length]; + for (int i = 0; i < args.length; i++) { + Class<?> thisClass = args[i].getClass(); + if (thisClass == Integer.class) + signature[i] = int.class; + else if (thisClass == Long.class) + signature[i] = long.class; + else if (thisClass == Byte.class) + signature[i] = byte.class; + else if (thisClass == Boolean.class) + signature[i] = boolean.class; + else + signature[i] = thisClass; + } + Method method = clazz.getMethod(methodName, signature); + return method.invoke(target, args); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private Class<?> classForName(String classname) { + try { + Class<?> cls = Class.forName(classname); + return cls; + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + + private Object invoke(Class<?> cls, String methodName) { + return invoke(cls, methodName, (Class<?>[]) null, (Object[]) null); + } + + private Object invoke(Class<?> cls, String methodName, Class<?>[] paramTypes, + Object... arguments) { + try { + Method m = cls.getDeclaredMethod(methodName, paramTypes); + return m.invoke(null, arguments); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private Object invoke(Object obj, String methodName) { + return invoke(obj, methodName, (Class<?>[]) null, (Object[]) null); + } + + private Object invoke(Object obj, String methodName, Class<?>[] paramTypes, Object... arguments) { + try { + Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes); + return m.invoke(obj, arguments); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } +} diff --git a/swtmenubar/src/com/android/menubar/IMenuBarCallback.java b/swtmenubar/src/com/android/menubar/IMenuBarCallback.java new file mode 100644 index 0000000..b0d6568 --- /dev/null +++ b/swtmenubar/src/com/android/menubar/IMenuBarCallback.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 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 com.android.menubar; + + + +/** + * Callbacks used by {@link IMenuBarEnhancer}. + */ +public interface IMenuBarCallback { + /** + * Invoked when the About menu item is selected by the user. + */ + abstract public void onAboutMenuSelected(); + + /** + * Invoked when the Preferences or Options menu item is selected by the user. + */ + abstract public void onPreferencesMenuSelected(); + + /** + * Used by the enhancer implementations to report errors. + * + * @param format A printf-like format string. + * @param args The parameters for the printf-like format string. + */ + abstract public void printError(String format, Object...args); +} diff --git a/swtmenubar/src/com/android/menubar/IMenuBarEnhancer.java b/swtmenubar/src/com/android/menubar/IMenuBarEnhancer.java new file mode 100644 index 0000000..d835bd6 --- /dev/null +++ b/swtmenubar/src/com/android/menubar/IMenuBarEnhancer.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011 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 com.android.menubar; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; + + +/** + * Interface to the platform-specific MenuBarEnhancer implementation returned by + * {@link MenuBarEnhancer#setupMenu}. + */ +public interface IMenuBarEnhancer { + + /** Values that indicate how the menu bar is being handlded. */ + public enum MenuBarMode { + /** + * The Mac-specific About and Preferences are being used. + * No File > Exit menu should be provided by the application. + */ + MAC_OS, + /** + * The provided SWT {@link Menu} is being used for About and Options. + * The application should provide a File > Exit menu. + */ + GENERIC + } + + /** + * Returns a {@link MenuBarMode} enum that indicates how the menu bar is going to + * or has been modified. This is implementation specific and can be called before or + * after {@link #setupMenu}. + * <p/> + * Callers would typically call that to know if they need to hide or display + * menu items. For example when {@link MenuBarMode#MAC_OS} is used, an app + * would typically not need to provide any "File > Exit" menu item. + * + * @return One of the {@link MenuBarMode} values. + */ + public MenuBarMode getMenuBarMode(); + + /** + * Updates the menu bar to provide an About menu item and a Preferences menu item. + * Depending on the platform, the menu items might be decorated with the + * given {@code appName}. + * <p/> + * Users should not call this directly. + * {@link MenuBarEnhancer#setupMenu} should be used instead. + * + * @param appName Name used for the About menu item and similar. Must not be null. + * @param display The SWT display. Must not be null. + * @param callbacks Callbacks called when "About" and "Preferences" menu items are invoked. + * Must not be null. + */ + public void setupMenu( + String appName, + Display display, + IMenuBarCallback callbacks); +} diff --git a/swtmenubar/src/com/android/menubar/MenuBarEnhancer.java b/swtmenubar/src/com/android/menubar/MenuBarEnhancer.java new file mode 100644 index 0000000..eb3e817 --- /dev/null +++ b/swtmenubar/src/com/android/menubar/MenuBarEnhancer.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2011 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 com.android.menubar; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; + + +/** + * On Mac, {@link MenuBarEnhancer#setupMenu} plugs a listener on the About and the + * Preferences menu items of the standard "application" menu in the menu bar. + * On Windows or Linux, it adds relevant items to a given {@link Menu} linked to + * the same listeners. + */ +public final class MenuBarEnhancer { + + private MenuBarEnhancer() { + } + + /** + * Creates an instance of {@link IMenuBarEnhancer} specific to the current platform + * and invoke its {@link IMenuBarEnhancer#setupMenu} to updates the menu bar. + * <p/> + * Depending on the platform, this will either hook into the existing About menu item + * and a Preferences or Options menu item or add new ones to the given {@code swtMenu}. + * Depending on the platform, the menu items might be decorated with the + * given {@code appName}. + * <p/> + * Potential errors are reported through {@link IMenuBarCallback}. + * + * @param appName Name used for the About menu item and similar. Must not be null. + * @param swtMenu For non-mac platform this is the menu where the "About" and + * the "Options" menu items are created. Typically the menu might be + * called "Tools". Must not be null. + * @param callbacks Callbacks called when "About" and "Preferences" menu items are invoked. + * Must not be null. + * @return A actual {@link IMenuBarEnhancer} implementation. Never null. + * This is currently not of any use for the caller but is left in case + * we want to expand the functionality later. + */ + public static IMenuBarEnhancer setupMenu( + String appName, + final Menu swtMenu, + IMenuBarCallback callbacks) { + + IMenuBarEnhancer enhancer = getEnhancer(callbacks); + + // Default implementation for generic platforms + if (enhancer == null) { + enhancer = new IMenuBarEnhancer() { + + public MenuBarMode getMenuBarMode() { + return MenuBarMode.GENERIC; + } + + public void setupMenu( + String appName, + Display display, + final IMenuBarCallback callbacks) { + if (swtMenu.getItemCount() > 0) { + new MenuItem(swtMenu, SWT.SEPARATOR); + } + + // Note: we use "Preferences" on Mac and "Options" on Windows/Linux. + final MenuItem pref = new MenuItem(swtMenu, SWT.NONE); + pref.setText("&Options..."); + + final MenuItem about = new MenuItem(swtMenu, SWT.NONE); + about.setText("&About..."); + + pref.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + try { + pref.setEnabled(false); + callbacks.onPreferencesMenuSelected(); + super.widgetSelected(e); + } finally { + pref.setEnabled(true); + } + } + }); + + about.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + try { + about.setEnabled(false); + callbacks.onAboutMenuSelected(); + super.widgetSelected(e); + } finally { + about.setEnabled(true); + } + } + }); + } + }; + } + + enhancer.setupMenu(appName, swtMenu.getDisplay(), callbacks); + return enhancer; + } + + + public static IMenuBarEnhancer setupMenuManager( + String appName, + Display display, + final IMenuManager menuManager, + final IAction aboutAction, + final IAction preferencesAction, + final IAction quitAction) { + + IMenuBarCallback callbacks = new IMenuBarCallback() { + public void printError(String format, Object... args) { + System.err.println(String.format(format, args)); + } + + public void onPreferencesMenuSelected() { + if (preferencesAction != null) { + preferencesAction.run(); + } + } + + public void onAboutMenuSelected() { + if (aboutAction != null) { + aboutAction.run(); + } + } + }; + + IMenuBarEnhancer enhancer = getEnhancer(callbacks); + + // Default implementation for generic platforms + if (enhancer == null) { + enhancer = new IMenuBarEnhancer() { + + public MenuBarMode getMenuBarMode() { + return MenuBarMode.GENERIC; + } + + public void setupMenu( + String appName, + Display display, + final IMenuBarCallback callbacks) { + if (!menuManager.isEmpty()) { + menuManager.add(new Separator()); + } + + if (aboutAction != null) { + menuManager.add(aboutAction); + } + if (preferencesAction != null) { + menuManager.add(preferencesAction); + } + if (quitAction != null) { + if (aboutAction != null || preferencesAction != null) { + menuManager.add(new Separator()); + } + menuManager.add(quitAction); + } + } + }; + } + + enhancer.setupMenu(appName, display, callbacks); + return enhancer; + } + + private static IMenuBarEnhancer getEnhancer(IMenuBarCallback callbacks) { + IMenuBarEnhancer enhancer = null; + String p = SWT.getPlatform(); + String className = null; + if ("carbon".equals(p)) { //$NON-NLS-1$ + className = "com.android.menubar.internal.MenuBarEnhancerCarbon"; //$NON-NLS-1$ + } else if ("cocoa".equals(p)) { //$NON-NLS-1$ + className = "com.android.menubar.internal.MenuBarEnhancerCocoa"; //$NON-NLS-1$ + } + + if (System.getenv("DEBUG_SWTMENUBAR") != null) { + callbacks.printError("DEBUG SwtMenuBar: SWT=%1$s, class=%2$s", p, className); + } + + if (className != null) { + try { + Class<?> clazz = Class.forName(className); + enhancer = (IMenuBarEnhancer) clazz.newInstance(); + } catch (Exception e) { + // Log an error and fallback on the default implementation. + callbacks.printError( + "Failed to instantiate %1$s: %2$s", //$NON-NLS-1$ + className, + e.toString()); + } + } + return enhancer; + } +} diff --git a/traceview/NOTICE b/traceview/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/traceview/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/traceview/src/com/android/traceview/MainWindow.java b/traceview/src/com/android/traceview/MainWindow.java index 1a8e96c..b78b4f7 100644 --- a/traceview/src/com/android/traceview/MainWindow.java +++ b/traceview/src/com/android/traceview/MainWindow.java @@ -22,6 +22,7 @@ import org.eclipse.jface.window.ApplicationWindow; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; @@ -34,6 +35,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.channels.FileChannel; import java.util.HashMap; import java.util.Properties; @@ -41,7 +43,6 @@ import java.util.Properties; public class MainWindow extends ApplicationWindow { private final static String PING_NAME = "Traceview"; - private final static String PING_VERSION = "1.0"; private TraceReader mReader; private String mTraceName; @@ -64,6 +65,13 @@ public class MainWindow extends ApplicationWindow { protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText("Traceview: " + mTraceName); + + InputStream in = getClass().getClassLoader().getResourceAsStream( + "icons/traceview-128.png"); + if (in != null) { + shell.setImage(new Image(shell.getDisplay(), in)); + } + shell.setBounds(100, 10, 1282, 900); } diff --git a/traceview/src/com/android/traceview/TimeLineView.java b/traceview/src/com/android/traceview/TimeLineView.java index 875becf..bc565dd 100644 --- a/traceview/src/com/android/traceview/TimeLineView.java +++ b/traceview/src/com/android/traceview/TimeLineView.java @@ -22,6 +22,7 @@ import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; @@ -284,6 +285,12 @@ public class TimeLineView extends Composite implements Observer { } }); + mSurface.addMouseWheelListener(new MouseWheelListener() { + public void mouseScrolled(MouseEvent me) { + mSurface.mouseScrolled(me); + } + }); + mTimescale.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent me) { @@ -842,7 +849,7 @@ public class TimeLineView extends Composite implements Observer { } private static enum GraphicsState { - Normal, Marking, Scaling, Animating + Normal, Marking, Scaling, Animating, Scrolling }; private class Surface extends Canvas { @@ -966,7 +973,8 @@ public class TimeLineView extends Composite implements Observer { int xdim = dim.x - TotalXMargin; mScaleInfo.setNumPixels(xdim); boolean forceEndPoints = (mGraphicsState == GraphicsState.Scaling - || mGraphicsState == GraphicsState.Animating); + || mGraphicsState == GraphicsState.Animating + || mGraphicsState == GraphicsState.Scrolling); mScaleInfo.computeTicks(forceEndPoints); mCachedMinVal = mScaleInfo.getMinVal(); mCachedMaxVal = mScaleInfo.getMaxVal(); @@ -1687,6 +1695,44 @@ public class TimeLineView extends Composite implements Observer { update(); } + private void mouseScrolled(MouseEvent me) { + mGraphicsState = GraphicsState.Scrolling; + double tMin = mScaleInfo.getMinVal(); + double tMax = mScaleInfo.getMaxVal(); + double zoomFactor = 2; + double tMinRef = mLimitMinVal; + double tMaxRef = mLimitMaxVal; + double t; // the fixed point + double tMinNew; + double tMaxNew; + if (me.count > 0) { + // we zoom in + Point dim = mSurface.getSize(); + int x = me.x; + if (x < LeftMargin) + x = LeftMargin; + if (x > dim.x - RightMargin) + x = dim.x - RightMargin; + double ppr = mScaleInfo.getPixelsPerRange(); + t = tMin + ((x - LeftMargin) / ppr); + tMinNew = Math.max(tMinRef, t - (t - tMin) / zoomFactor); + tMaxNew = Math.min(tMaxRef, t + (tMax - t) / zoomFactor); + } else { + // we zoom out + double factor = (tMax - tMin) / (tMaxRef - tMinRef); + if (factor < 1) { + t = (factor * tMinRef - tMin) / (factor - 1); + tMinNew = Math.max(tMinRef, t - zoomFactor * (t - tMin)); + tMaxNew = Math.min(tMaxRef, t + zoomFactor * (tMax - t)); + } else { + return; + } + } + mScaleInfo.setMinVal(tMinNew); + mScaleInfo.setMaxVal(tMaxNew); + mSurface.redraw(); + } + // No defined behavior yet for double-click. private void mouseDoubleClick(MouseEvent me) { } diff --git a/traceview/src/resources/icons/traceview-128.png b/traceview/src/resources/icons/traceview-128.png Binary files differnew file mode 100644 index 0000000..5b4eff1 --- /dev/null +++ b/traceview/src/resources/icons/traceview-128.png |