diff --git a/apps/CustomLocale/NOTICE b/apps/CustomLocale/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/apps/CustomLocale/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/apps/Development/NOTICE b/apps/Development/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/apps/Development/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/apps/SdkSetup/NOTICE b/apps/SdkSetup/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/apps/SdkSetup/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/apps/SpareParts/NOTICE b/apps/SpareParts/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/apps/SpareParts/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/apps/launchperf/NOTICE b/apps/launchperf/NOTICE
new file mode 100644
index 000000000..c5b1efa7a
--- /dev/null
+++ b/apps/launchperf/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/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index 55c18bb26..c0dfcefd5 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -41,7 +41,9 @@ Require-Bundle: com.android.ide.eclipse.ddms,
org.eclipse.wst.xml.core,
org.eclipse.wst.xml.ui,
org.eclipse.jdt.junit,
- org.eclipse.jdt.junit.runtime
+ org.eclipse.jdt.junit.runtime,
+ org.eclipse.ltk.core.refactoring,
+ org.eclipse.ltk.ui.refactoring
Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt,
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index c18c72f05..2bf633d32 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -470,7 +470,7 @@
point="org.eclipse.ui.actionSets">
-
-
-
-
+
+
+
+
@@ -506,7 +515,7 @@
delegateDescription="Removes the Android JAR from the Bootstrap Classpath"
id="com.android.ide.eclipse.adt.launch.JUnitLaunchConfigDelegate.launchAndroidJunit"
modes="run,debug"
- name="Android JUnit"
+ name="Android JUnit Test"
type="org.eclipse.jdt.junit.launchconfig">
@@ -516,7 +525,7 @@
delegate="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchConfigDelegate"
id="com.android.ide.eclipse.adt.junit.launchConfigurationType"
modes="run,debug"
- name="Android Instrumentation"
+ name="Android JUnit Test"
public="true"
sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"
sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer">
@@ -534,7 +543,7 @@
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
@@ -544,7 +553,7 @@
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchShortcut"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.junit.launchShortcut"
- label="Android Instrumentation"
+ label="Android JUnit Test"
modes="run,debug">
@@ -565,4 +574,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index e0708f32b..42db64a61 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -1282,7 +1282,7 @@ public class AdtPlugin extends AbstractUIPlugin {
AdtPlugin.PLUGIN_ID,
UNKNOWN_EDITOR);
try {
- file.setPersistentProperty(qname, "1");
+ file.setPersistentProperty(qname, "1"); //$NON-NLS-1$
} catch (CoreException e) {
// pass
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
index f8a969e94..bc5b01c5f 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
@@ -196,7 +196,7 @@ public class ApkBuilder extends BaseBuilder {
}
// build() returns a list of project from which this project depends for future compilation.
- @SuppressWarnings("unchecked") //$NON-NLS-1$
+ @SuppressWarnings("unchecked")
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
index 8aa1abad8..6f9c2f16a 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java
@@ -197,7 +197,7 @@ public class PreCompilerBuilder extends BaseBuilder {
}
// build() returns a list of project from which this project depends for future compilation.
- @SuppressWarnings("unchecked") //$NON-NLS-1$
+ @SuppressWarnings("unchecked")
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java
index 035aa5b73..b1f9ec1d4 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java
@@ -55,7 +55,7 @@ public class ResourceManagerBuilder extends BaseBuilder {
}
// build() returns a list of project from which this project depends for future compilation.
- @SuppressWarnings("unchecked") //$NON-NLS-1$
+ @SuppressWarnings("unchecked")
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchConfiguration.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchConfiguration.java
index 448cda6b5..3e610db08 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchConfiguration.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchConfiguration.java
@@ -32,8 +32,30 @@ public class AndroidLaunchConfiguration {
*/
public int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
- public static final boolean AUTO_TARGET_MODE = true;
+ public enum TargetMode {
+ AUTO(true), MANUAL(false);
+
+ private boolean mValue;
+ TargetMode(boolean value) {
+ mValue = value;
+ }
+
+ public boolean getValue() {
+ return mValue;
+ }
+
+ public static TargetMode getMode(boolean value) {
+ for (TargetMode mode : values()) {
+ if (mode.mValue == value) {
+ return mode;
+ }
+ }
+
+ return null;
+ }
+ }
+
/**
* Target selection mode.
*
@@ -41,7 +63,7 @@ public class AndroidLaunchConfiguration {
* false: manual mode
*
*/
- public boolean mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE;
+ public TargetMode mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE;
/**
* Indicates whether the emulator should be called with -wipe-data
@@ -81,8 +103,9 @@ public class AndroidLaunchConfiguration {
}
try {
- mTargetMode = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
- mTargetMode);
+ boolean value = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
+ mTargetMode.getValue());
+ mTargetMode = TargetMode.getMode(value);
} catch (CoreException e) {
// nothing to be done here, we'll use the default value
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
index 499cca704..3aa0b91f6 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java
@@ -29,11 +29,15 @@ import com.android.ddmlib.MultiLineReceiver;
import com.android.ddmlib.SyncService;
import com.android.ddmlib.SyncService.SyncResult;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration.TargetMode;
import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo.InstallRetryMode;
import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk;
+import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
import com.android.sdklib.avd.AvdManager;
@@ -52,6 +56,9 @@ import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.ui.DebugUITools;
+import org.eclipse.jdt.core.IJavaModel;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IVMConnector;
import org.eclipse.jdt.launching.JavaRuntime;
@@ -64,6 +71,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -236,7 +244,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// set default target mode
wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
- LaunchConfigDelegate.DEFAULT_TARGET_MODE);
+ LaunchConfigDelegate.DEFAULT_TARGET_MODE.getValue());
// default AVD: None
wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String) null);
@@ -332,6 +340,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
Sdk currentSdk = Sdk.getCurrent();
AvdManager avdManager = currentSdk.getAvdManager();
+ // reload the AVDs to make sure we are up to date
+ try {
+ avdManager.reloadAvds();
+ } catch (AndroidLocationException e1) {
+ // this happens if the AVD Manager failed to find the folder in which the AVDs are
+ // stored. This is unlikely to happen, but if it does, we should force to go manual
+ // to allow using physical devices.
+ config.mTargetMode = TargetMode.MANUAL;
+ }
+
// get the project target
final IAndroidTarget projectTarget = currentSdk.getTarget(project);
@@ -356,7 +374,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* If == 1, launch the application on this AVD/device.
*/
- if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) {
+ if (config.mTargetMode == TargetMode.AUTO) {
// if we are in automatic target mode, we need to find the current devices
IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
@@ -795,6 +813,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
return false;
}
+ // The app is now installed, now try the dependent projects
+ for (DelayedLaunchInfo dependentLaunchInfo : getDependenciesLaunchInfo(launchInfo)) {
+ String msg = String.format("Project dependency found, syncing: %s",
+ dependentLaunchInfo.getProject().getName());
+ AdtPlugin.printToConsole(launchInfo.getProject(), msg);
+ syncApp(dependentLaunchInfo, device);
+ }
+
return installResult;
}
@@ -806,6 +832,81 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
return false;
}
+ /**
+ * For the current launchInfo, create additional DelayedLaunchInfo that should be used to
+ * sync APKs that we are dependent on to the device.
+ *
+ * @param launchInfo the original launch info that we want to find the
+ * @return a list of DelayedLaunchInfo (may be empty if no dependencies were found or error)
+ */
+ public List getDependenciesLaunchInfo(DelayedLaunchInfo launchInfo) {
+ List dependencies = new ArrayList();
+
+ // Convert to equivalent JavaProject
+ IJavaProject javaProject;
+ try {
+ //assuming this is an Android (and Java) project since it is attached to the launchInfo.
+ javaProject = BaseProjectHelper.getJavaProject(launchInfo.getProject());
+ } catch (CoreException e) {
+ // return empty dependencies
+ AdtPlugin.printErrorToConsole(launchInfo.getProject(), e);
+ return dependencies;
+ }
+
+ // Get all projects that this depends on
+ List androidProjectList;
+ try {
+ androidProjectList = ProjectHelper.getAndroidProjectDependencies(javaProject);
+ } catch (JavaModelException e) {
+ // return empty dependencies
+ AdtPlugin.printErrorToConsole(launchInfo.getProject(), e);
+ return dependencies;
+ }
+
+ // for each project, parse manifest and create launch information
+ for (IJavaProject androidProject : androidProjectList) {
+ // Parse the Manifest to get various required information
+ // copied from LaunchConfigDelegate
+ AndroidManifestParser manifestParser;
+ try {
+ manifestParser = AndroidManifestParser.parse(
+ androidProject, null /* errorListener */,
+ true /* gatherData */, false /* markErrors */);
+ } catch (CoreException e) {
+ AdtPlugin.printErrorToConsole(
+ launchInfo.getProject(),
+ String.format("Error parsing manifest of %s",
+ androidProject.getElementName()));
+ continue;
+ }
+
+ // Get the APK location (can return null)
+ IFile apk = ProjectHelper.getApplicationPackage(androidProject.getProject());
+ if (apk == null) {
+ // getApplicationPackage will have logged an error message
+ continue;
+ }
+
+ // Create new launchInfo as an hybrid between parent and dependency information
+ DelayedLaunchInfo delayedLaunchInfo = new DelayedLaunchInfo(
+ androidProject.getProject(),
+ manifestParser.getPackage(),
+ launchInfo.getLaunchAction(),
+ apk,
+ manifestParser.getDebuggable(),
+ manifestParser.getApiLevelRequirement(),
+ launchInfo.getLaunch(),
+ launchInfo.getMonitor());
+
+ // Add to the list
+ dependencies.add(delayedLaunchInfo);
+ }
+
+ return dependencies;
+ }
+
+
+
/**
* Installs the application package that was pushed to a temporary location on the device.
* @param launchInfo The launch information
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java
index b898f63c5..3789153e4 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.launch;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration.TargetMode;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.ddms.DdmsPlugin;
@@ -292,14 +293,15 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
public void initializeFrom(ILaunchConfiguration configuration) {
AvdManager avdManager = Sdk.getCurrent().getAvdManager();
- boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic
+ TargetMode mode = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic
try {
- value = configuration.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, value);
+ mode = TargetMode.getMode(configuration.getAttribute(
+ LaunchConfigDelegate.ATTR_TARGET_MODE, mode.getValue()));
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
- mAutoTargetButton.setSelection(value);
- mManualTargetButton.setSelection(!value);
+ mAutoTargetButton.setSelection(mode.getValue());
+ mManualTargetButton.setSelection(!mode.getValue());
// look for the project name to get its target.
String stringValue = "";
@@ -354,7 +356,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
mPreferredAvdSelector.setSelection(null);
}
- value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
+ boolean value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
try {
value = configuration.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, value);
} catch (CoreException e) {
@@ -440,7 +442,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
*/
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
- LaunchConfigDelegate.DEFAULT_TARGET_MODE);
+ LaunchConfigDelegate.DEFAULT_TARGET_MODE.getValue());
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
LaunchConfigDelegate.DEFAULT_SPEED);
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java
index 80f62eaa8..d057ac709 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java
@@ -18,15 +18,14 @@ package com.android.ide.eclipse.adt.launch;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration.TargetMode;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
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.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
@@ -51,7 +50,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
/** Target mode parameters: true is automatic, false is manual */
public static final String ATTR_TARGET_MODE = AdtPlugin.PLUGIN_ID + ".target"; //$NON-NLS-1$
- public static final boolean DEFAULT_TARGET_MODE = true; //automatic mode
+ public static final TargetMode DEFAULT_TARGET_MODE = TargetMode.AUTO;
/**
* Launch action:
@@ -152,7 +151,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
AdtPlugin.printToConsole(project, "Android Launch!");
// check if the project is using the proper sdk.
- // if that throws an exception, we simply let it propage to the caller.
+ // if that throws an exception, we simply let it propagate to the caller.
if (checkAndroidProject(project) == false) {
AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!");
androidLaunch.stopLaunch();
@@ -215,7 +214,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
AndroidLaunchController controller = AndroidLaunchController.getInstance();
// get the application package
- IFile applicationPackage = getApplicationPackage(project);
+ IFile applicationPackage = ProjectHelper.getApplicationPackage(project);
if (applicationPackage == null) {
androidLaunch.stopLaunch();
return;
@@ -387,39 +386,6 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
}
- /**
- * Returns the android package file as an IFile object for the specified
- * project.
- * @param project The project
- * @return The android package as an IFile object or null if not found.
- */
- private IFile getApplicationPackage(IProject project) {
- // get the output folder
- IFolder outputLocation = BaseProjectHelper.getOutputFolder(project);
-
- if (outputLocation == null) {
- AdtPlugin.printErrorToConsole(project,
- "Failed to get the output location of the project. Check build path properties"
- );
- return null;
- }
-
-
- // get the package path
- String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
- IResource r = outputLocation.findMember(packageName);
-
- // check the package is present
- if (r instanceof IFile && r.exists()) {
- return (IFile)r;
- }
-
- String msg = String.format("Could not find %1$s!", packageName);
- AdtPlugin.printErrorToConsole(project, msg);
-
- return null;
- }
-
/**
* Returns the name of the activity.
*/
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
index 30b072389..91bd21cb7 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java
@@ -58,7 +58,7 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
/**
*
*/
- public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab.png";
+ public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab.png"; //$NON-NLS-1$
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
index 4dfe37d1d..b88026329 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchAction.java
@@ -20,7 +20,7 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.DelayedLaunchInfo;
import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
import com.android.ide.eclipse.adt.launch.junit.runtime.AndroidJUnitLaunchInfo;
-import com.android.ide.eclipse.adt.launch.junit.runtime.RemoteADTTestRunner;
+import com.android.ide.eclipse.adt.launch.junit.runtime.RemoteAdtTestRunner;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -166,7 +166,7 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
private final VMRunnerConfiguration mRunConfig;
private final ILaunch mLaunch;
private final AndroidJUnitLaunchInfo mJUnitInfo;
- private RemoteADTTestRunner mTestRunner = null;
+ private RemoteAdtTestRunner mTestRunner = null;
private boolean mIsTerminated = false;
TestRunnerProcess(VMRunnerConfiguration runConfig, ILaunch launch,
@@ -256,7 +256,7 @@ class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
*/
@Override
public void run() {
- mTestRunner = new RemoteADTTestRunner();
+ mTestRunner = new RemoteAdtTestRunner();
mTestRunner.runTests(mRunConfig.getProgramArguments(), mJUnitInfo);
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
index 05cc6ae73..a624b0001 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigDelegate.java
@@ -18,10 +18,11 @@ package com.android.ide.eclipse.adt.launch.junit;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.AndroidLaunch;
+import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration;
import com.android.ide.eclipse.adt.launch.AndroidLaunchController;
import com.android.ide.eclipse.adt.launch.IAndroidLaunchAction;
import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate;
-import com.android.ide.eclipse.adt.launch.AndroidLaunchConfiguration;
+import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
@@ -31,6 +32,7 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
+import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
@@ -46,6 +48,7 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
/** Launch config attribute that stores instrumentation runner */
static final String ATTR_INSTR_NAME = AdtPlugin.PLUGIN_ID + ".instrumentation"; //$NON-NLS-1$
+ static final String INSTRUMENTATION_OK = null;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
@Override
@@ -87,7 +90,8 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
* Helper method to return the set of instrumentations for the Android project
*
* @param project the {@link IProject} to get instrumentations for
- * @return null if no error occurred parsing instrumentations
+ * @return null if error occurred parsing instrumentations, otherwise returns array of
+ * instrumentation class names
*/
static String[] getInstrumentationsForProject(IProject project) {
if (project != null) {
@@ -117,4 +121,56 @@ public class AndroidJUnitLaunchConfigDelegate extends LaunchConfigDelegate {
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
TestKindRegistry.JUNIT3_TEST_KIND_ID);
}
+
+ /**
+ * Helper method to determine if specified instrumentation can be used as a test runner
+ *
+ * @param project the {@link IJavaProject} to validate
+ * @param instrumentation the instrumentation class name to validate
+ * @return INSTRUMENTATION_OK if valid, otherwise returns error message
+ */
+ static String validateInstrumentationRunner(IJavaProject project, String instrumentation) {
+ AndroidManifestParser manifestParser;
+ try {
+ manifestParser = AndroidManifestParser.parse(
+ project, null /* errorListener */,
+ true /* gatherData */, false /* markErrors */);
+ // check if this instrumentation is the standard test runner
+ if (!instrumentation.equals(AndroidConstants.CLASS_INSTRUMENTATION_RUNNER)) {
+ // check if it extends the standard test runner
+ String result = BaseProjectHelper.testClassForManifest(project,
+ instrumentation, AndroidConstants.CLASS_INSTRUMENTATION_RUNNER, true);
+ if (result != BaseProjectHelper.TEST_CLASS_OK) {
+ return String.format("The instrumentation runner must be of type %s",
+ AndroidConstants.CLASS_INSTRUMENTATION_RUNNER);
+ }
+ }
+ if (!hasTestRunnerLibrary(manifestParser)) {
+ return String.format("%s does not not use the %s library",
+ project.getProject().getName(), AndroidConstants.LIBRARY_TEST_RUNNER);
+ }
+ } catch (CoreException e) {
+ String err = String.format("Error parsing AndroidManifest for %s",
+ project.getProject().getName());
+ AdtPlugin.log(e, err);
+ return err;
+ }
+ return INSTRUMENTATION_OK;
+ }
+
+ /**
+ * Helper method to determine if given manifest has a AndroidConstants.LIBRARY_TEST_RUNNER
+ * library reference
+ *
+ * @param manifestParser the {@link AndroidManifestParser} to search
+ * @return true if test runner library found, false otherwise
+ */
+ static boolean hasTestRunnerLibrary(AndroidManifestParser manifestParser) {
+ for (String lib : manifestParser.getUsesLibraries()) {
+ if (lib.equals(AndroidConstants.LIBRARY_TEST_RUNNER)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
index 5fbda983d..aa59a5157 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchConfigurationTab.java
@@ -691,10 +691,18 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat
private void validateInstrumentation(IJavaProject javaProject) {
if (mInstrumentations == null || mInstrumentations.length < 1) {
setErrorMessage("Specified project has no defined instrumentations");
+ return;
}
String instrumentation = getSelectedInstrumentation();
if (instrumentation == null) {
setErrorMessage("Instrumentation not specified");
+ return;
+ }
+ String result = AndroidJUnitLaunchConfigDelegate.validateInstrumentationRunner(
+ javaProject, instrumentation);
+ if (result != AndroidJUnitLaunchConfigDelegate.INSTRUMENTATION_OK) {
+ setErrorMessage(result);
+ return;
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java
index e03f2822b..30649e2e8 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/AndroidJUnitLaunchShortcut.java
@@ -16,6 +16,9 @@
package com.android.ide.eclipse.adt.launch.junit;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.common.AndroidConstants;
+
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
@@ -34,7 +37,7 @@ public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut {
/**
* Creates a default Android JUnit launch configuration. Sets the instrumentation runner to the
- * first instrumentation found in the AndroidManifest.
+ * first instrumentation found in the AndroidManifest.
*/
@Override
protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(IJavaElement element)
@@ -43,10 +46,27 @@ public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut {
IProject project = element.getResource().getProject();
String[] instrumentations =
AndroidJUnitLaunchConfigDelegate.getInstrumentationsForProject(project);
- if (instrumentations != null && instrumentations.length > 0) {
- // just pick the first runner
- config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
- instrumentations[0]);
+ boolean runnerFound = false;
+ if (instrumentations != null) {
+ // just pick the first valid runner
+ for (String instr : instrumentations) {
+ if (AndroidJUnitLaunchConfigDelegate.validateInstrumentationRunner(
+ element.getJavaProject(), instr) ==
+ AndroidJUnitLaunchConfigDelegate.INSTRUMENTATION_OK) {
+
+ config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
+ instr);
+ runnerFound = true;
+ break;
+ }
+ }
+ }
+ if (!runnerFound) {
+ // TODO: put this in a string properties
+ String msg = String.format("ERROR: Application does not specify a %s instrumentation or does not declare uses-library %s",
+ AndroidConstants.CLASS_INSTRUMENTATION_RUNNER,
+ AndroidConstants.LIBRARY_TEST_RUNNER);
+ AdtPlugin.printErrorToConsole(project, msg);
}
AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteADTTestRunner.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java
similarity index 98%
rename from tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteADTTestRunner.java
rename to tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java
index 6834c089a..0a6a3daee 100755
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteADTTestRunner.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/junit/runtime/RemoteAdtTestRunner.java
@@ -34,7 +34,7 @@ import org.eclipse.jdt.internal.junit.runner.TestReferenceFailure;
* @see org.eclipse.jdt.internal.junit.runner.RemoteTestRunner for more details on the protocol
*/
@SuppressWarnings("restriction")
-public class RemoteADTTestRunner extends RemoteTestRunner {
+public class RemoteAdtTestRunner extends RemoteTestRunner {
private AndroidJUnitLaunchInfo mLaunchInfo;
private TestExecution mExecution;
@@ -97,6 +97,8 @@ public class RemoteADTTestRunner extends RemoteTestRunner {
// error occurred during test collection.
reportError(collector.getErrorMessage());
// abort here
+ notifyTestRunEnded(0);
+ return;
}
notifyTestRunStarted(collector.getTestCaseCount());
collector.sendTrees(this);
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
index c650b9846..e091b1385 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
@@ -20,8 +20,10 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
@@ -34,12 +36,14 @@ import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.JavaRuntime;
import java.util.ArrayList;
+import java.util.List;
/**
* Utility class to manipulate Project parameters/properties.
@@ -679,4 +683,71 @@ public final class ProjectHelper {
return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
}
+
+ /**
+ * Find the list of projects on which this JavaProject is dependent on at the compilation level.
+ *
+ * @param javaProject Java project that we are looking for the dependencies.
+ * @return A list of Java projects for which javaProject depend on.
+ * @throws JavaModelException
+ */
+ public static List getAndroidProjectDependencies(IJavaProject javaProject)
+ throws JavaModelException {
+ String[] requiredProjectNames = javaProject.getRequiredProjectNames();
+
+ // Go from java project name to JavaProject name
+ IJavaModel javaModel = javaProject.getJavaModel();
+
+ // loop through all dependent projects and keep only those that are Android projects
+ List projectList = new ArrayList(requiredProjectNames.length);
+ for (String javaProjectName : requiredProjectNames) {
+ IJavaProject androidJavaProject = javaModel.getJavaProject(javaProjectName);
+
+ //Verify that the project has also the Android Nature
+ try {
+ if (!androidJavaProject.getProject().hasNature(AndroidConstants.NATURE)) {
+ continue;
+ }
+ } catch (CoreException e) {
+ continue;
+ }
+
+ projectList.add(androidJavaProject);
+ }
+
+ return projectList;
+ }
+
+ /**
+ * Returns the android package file as an IFile object for the specified
+ * project.
+ * @param project The project
+ * @return The android package as an IFile object or null if not found.
+ */
+ public static IFile getApplicationPackage(IProject project) {
+ // get the output folder
+ IFolder outputLocation = BaseProjectHelper.getOutputFolder(project);
+
+ if (outputLocation == null) {
+ AdtPlugin.printErrorToConsole(project,
+ "Failed to get the output location of the project. Check build path properties"
+ );
+ return null;
+ }
+
+
+ // get the package path
+ String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
+ IResource r = outputLocation.findMember(packageName);
+
+ // check the package is present
+ if (r instanceof IFile && r.exists()) {
+ return (IFile)r;
+ }
+
+ String msg = String.format("Could not find %1$s!", packageName);
+ AdtPlugin.printErrorToConsole(project, msg);
+
+ return null;
+ }
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringAction.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringAction.java
new file mode 100644
index 000000000..528701573
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringAction.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2009 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.refactorings.extractstring;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.ITypeRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.ui.JavaUI;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
+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;
+
+/*
+ * Quick Reference Link:
+ * http://www.eclipse.org/articles/article.php?file=Article-Unleashing-the-Power-of-Refactoring/index.html
+ * and
+ * http://www.ibm.com/developerworks/opensource/library/os-ecjdt/
+ */
+
+/**
+ * Action executed when the "Extract String" menu item is invoked.
+ *
+ * The intent of the action is to start a refactoring that extracts a source string and
+ * replaces it by an Android string resource ID.
+ *
+ * Workflow:
+ *
+ * - The action is currently located in the Refactoring menu in the main menu.
+ *
- TODO: extend the popup refactoring menu in a Java or Android XML file.
+ *
- The action is only enabled if the selection is 1 character or more. That is at least part
+ * of the string must be selected, it's not enough to just move the insertion point. This is
+ * a limitation due to {@link #selectionChanged(IAction, ISelection)} not being called when
+ * the insertion point is merely moved. TODO: address this limitation.
+ *
The action gets the current {@link ISelection}. It also knows the current
+ * {@link IWorkbenchWindow}. However for the refactoring we are also interested in having the
+ * actual resource file. By looking at the Active Window > Active Page > Active Editor we
+ * can get the {@link IEditorInput} and find the {@link ICompilationUnit} (aka Java file)
+ * that is being edited.
+ * TODO: change this to find the {@link IFile} being manipulated. The {@link ICompilationUnit}
+ * can be inferred using {@link JavaCore#createCompilationUnitFrom(IFile)}. This will allow
+ * us to be able to work with a selection from an Android XML file later.
+ * - The action creates a new {@link ExtractStringRefactoring} and make it run on in a new
+ * {@link ExtractStringWizard}.
+ *
+ */
+public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
+
+ /** Keep track of the current workbench window. */
+ private IWorkbenchWindow mWindow;
+ private ITextSelection mSelection;
+ private ICompilationUnit mUnit;
+
+ /**
+ * Keep track of the current workbench window.
+ */
+ public void init(IWorkbenchWindow window) {
+ mWindow = window;
+ }
+
+ public void dispose() {
+ // Nothing to do
+ }
+
+ /**
+ * Examine the selection to determine if the action should be enabled or not.
+ *
+ * Keep a link to the relevant selection structure (i.e. a part of the Java AST).
+ */
+ public void selectionChanged(IAction action, ISelection selection) {
+
+ // 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.
+
+ mSelection = null;
+ mUnit = null;
+
+ if (selection instanceof ITextSelection) {
+ mSelection = (ITextSelection) selection;
+ if (mSelection.getLength() > 0) {
+ mUnit = getCompilationUnit();
+ }
+
+ // Keep for debugging purposes
+ //System.out.println(String.format("-- Selection: %d + %d = %s",
+ // mSelection.getOffset(),
+ // mSelection.getLength(),
+ // mSelection.getText()));
+ }
+
+ action.setEnabled(mSelection != null && mUnit != null);
+ }
+
+ /**
+ * Create a new instance of our refactoring and a wizard to configure it.
+ */
+ public void run(IAction action) {
+ if (mSelection != null && mUnit != null) {
+ ExtractStringRefactoring ref = new ExtractStringRefactoring(mUnit, mSelection);
+ RefactoringWizard wizard = new ExtractStringWizard(ref, "Extract Android String");
+ RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
+ try {
+ op.run(mWindow.getShell(), wizard.getDefaultPageTitle());
+ } catch (InterruptedException e) {
+ // Interrupted. Pass.
+ }
+ }
+ }
+
+ /**
+ * Returns the active {@link ICompilationUnit} or null.
+ */
+ private ICompilationUnit getCompilationUnit() {
+ IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (wwin != null) {
+ IWorkbenchPage page = wwin.getActivePage();
+ if (page != null) {
+ IEditorPart editor = page.getActiveEditor();
+ if (editor != null) {
+ IEditorInput input = editor.getEditorInput();
+ if (input != null) {
+ ITypeRoot typeRoot = JavaUI.getEditorInputTypeRoot(input);
+ // The type root can be either a .class or a .java (aka compilation unit).
+ // We want the compilation unit kind.
+ if (typeRoot instanceof ICompilationUnit) {
+ return (ICompilationUnit) typeRoot;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringContribution.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringContribution.java
new file mode 100644
index 000000000..465e1a36c
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringContribution.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2009 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.refactorings.extractstring;
+
+import org.eclipse.ltk.core.refactoring.RefactoringContribution;
+import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
+
+import java.util.Map;
+
+/**
+ * @see ExtractStringDescriptor
+ */
+public class ExtractStringContribution extends RefactoringContribution {
+
+ /* (non-Javadoc)
+ * @see org.eclipse.ltk.core.refactoring.RefactoringContribution#createDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Map, int)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public RefactoringDescriptor createDescriptor(
+ String id,
+ String project,
+ String description,
+ String comment,
+ Map arguments,
+ int flags)
+ throws IllegalArgumentException {
+ return new ExtractStringDescriptor(project, description, comment, arguments);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map retrieveArgumentMap(RefactoringDescriptor descriptor) {
+ if (descriptor instanceof ExtractStringDescriptor) {
+ return ((ExtractStringDescriptor) descriptor).getArguments();
+ }
+ return super.retrieveArgumentMap(descriptor);
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringDescriptor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringDescriptor.java
new file mode 100644
index 000000000..6e999e942
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringDescriptor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2009 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.refactorings.extractstring;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+
+import java.util.Map;
+
+/**
+ * A descriptor that allows an {@link ExtractStringRefactoring} to be created from
+ * a previous instance of itself.
+ */
+public class ExtractStringDescriptor extends RefactoringDescriptor {
+
+ public static final String ID =
+ "com.android.ide.eclipse.adt.refactoring.extract.string"; //$NON-NLS-1$
+
+ private final Map mArguments;
+
+ public ExtractStringDescriptor(String project, String description, String comment,
+ Map arguments) {
+ super(ID, project, description, comment,
+ RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE //flags
+ );
+ mArguments = arguments;
+ }
+
+ public Map getArguments() {
+ return mArguments;
+ }
+
+ /**
+ * Creates a new refactoring instance for this refactoring descriptor based on
+ * an argument map. The argument map is created by the refactoring itself in
+ * {@link ExtractStringRefactoring#createChange(org.eclipse.core.runtime.IProgressMonitor)}
+ *
+ * This is apparently used to replay a refactoring.
+ *
+ * {@inheritDoc}
+ *
+ * @throws CoreException
+ */
+ @Override
+ public Refactoring createRefactoring(RefactoringStatus status) throws CoreException {
+ try {
+ ExtractStringRefactoring ref = new ExtractStringRefactoring(mArguments);
+ return ref;
+ } catch (NullPointerException e) {
+ status.addFatalError("Failed to recreate ExtractStringRefactoring from descriptor");
+ return null;
+ }
+ }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
new file mode 100644
index 000000000..cb449f02e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringInputPage.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2009 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.refactorings.extractstring;
+
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/**
+ * @see ExtractStringRefactoring
+ */
+class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage {
+
+ public ExtractStringInputPage() {
+ super("ExtractStringInputPage"); //$NON-NLS-1$
+ }
+
+ private Label mStringLabel;
+ private Text mNewIdTextField;
+ private Label mFileLabel;
+
+ /**
+ * Create the UI for the refactoring wizard.
+ *
+ * Note that at that point the initial conditions have been checked in
+ * {@link ExtractStringRefactoring}.
+ */
+ public void createControl(Composite parent) {
+
+ final ExtractStringRefactoring ref = getOurRefactoring();
+
+ Composite content = new Composite(parent, SWT.NONE);
+
+ GridLayout layout = new GridLayout();
+ layout.numColumns = 2;
+ content.setLayout(layout);
+
+ // line 1: String found in selection
+
+ Label label = new Label(content, SWT.NONE);
+ label.setText("String:");
+
+ String selectedString = ref.getTokenString();
+
+ mStringLabel = new Label(content, SWT.NONE);
+ mStringLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mStringLabel.setText(selectedString != null ? selectedString : "");
+
+ // TODO provide an option to replace all occurences of this string instead of
+ // just the one.
+
+ // line 2 : Textfield for new ID
+
+ label = new Label(content, SWT.NONE);
+ label.setText("Replace by R.string.");
+
+ mNewIdTextField = new Text(content, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
+ mNewIdTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mNewIdTextField.setText(guessId(selectedString));
+
+ ref.setReplacementStringId(mNewIdTextField.getText().trim());
+
+ mNewIdTextField.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ if (validatePage(ref)) {
+ ref.setReplacementStringId(mNewIdTextField.getText().trim());
+ }
+ }
+ });
+
+ // line 3: selection of the output file
+ // TODO add a file field/chooser combo to let the user select the file to edit.
+
+ label = new Label(content, SWT.NONE);
+ label.setText("Resource file:");
+
+ mFileLabel = new Label(content, SWT.NONE);
+ mFileLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mFileLabel.setText("/res/values/strings.xml");
+ ref.setTargetFile(mFileLabel.getText());
+
+ // line 4: selection of the res config
+ // TODO add the Configuration Selector to decide with strings.xml to change
+
+ label = new Label(content, SWT.NONE);
+ label.setText("Configuration:");
+
+ label = new Label(content, SWT.NONE);
+ label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ label.setText("default");
+
+ validatePage(ref);
+ setControl(content);
+ }
+
+ private String guessId(String text) {
+ // make lower case
+ text = text.toLowerCase();
+
+ // everything not alphanumeric becomes an underscore
+ text = text.replaceAll("[^a-zA-Z0-9]+", "_"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ // the id must be a proper Java identifier, so it can't start with a number
+ if (text.length() > 0 && !Character.isJavaIdentifierStart(text.charAt(0))) {
+ text = "_" + text; //$NON-NLS-1$
+ }
+ return text;
+ }
+
+ private ExtractStringRefactoring getOurRefactoring() {
+ return (ExtractStringRefactoring) getRefactoring();
+ }
+
+ private boolean validatePage(ExtractStringRefactoring ref) {
+ String text = mNewIdTextField.getText().trim();
+ boolean success = true;
+
+ // Analyze fatal errors.
+
+ if (text == null || text.length() < 1) {
+ setErrorMessage("Please provide a resource ID to replace with.");
+ success = false;
+ } else {
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ boolean ok = i == 0 ?
+ Character.isJavaIdentifierStart(c) :
+ Character.isJavaIdentifierPart(c);
+ if (!ok) {
+ setErrorMessage(String.format(
+ "The resource ID must be a valid Java identifier. The character %1$c at position %2$d is not acceptable.",
+ c, i+1));
+ success = false;
+ break;
+ }
+ }
+ }
+
+ // Analyze info & warnings.
+
+ if (success) {
+ if (ref.isResIdDuplicate(mFileLabel.getText(), text)) {
+ setErrorMessage(null);
+ setMessage(
+ String.format("Warning: There's already a string item called '%1$s' in %2$s.",
+ text, mFileLabel.getText()));
+ } else {
+ setMessage(null);
+ setErrorMessage(null);
+ }
+ }
+
+ setPageComplete(success);
+ return success;
+ }
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringRefactoring.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringRefactoring.java
new file mode 100644
index 000000000..715503c1f
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringRefactoring.java
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2009 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.refactorings.extractstring;
+
+import com.android.ide.eclipse.common.AndroidConstants;
+import com.android.ide.eclipse.common.project.AndroidManifestParser;
+import com.android.ide.eclipse.common.project.AndroidXPathFactory;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourceAttributes;
+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.core.runtime.SubMonitor;
+import org.eclipse.jdt.core.IBuffer;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.ToolFactory;
+import org.eclipse.jdt.core.compiler.IScanner;
+import org.eclipse.jdt.core.compiler.ITerminalSymbols;
+import org.eclipse.jdt.core.compiler.InvalidInputException;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.Name;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.StringLiteral;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
+import org.eclipse.jface.text.ITextSelection;
+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.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+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.text.edits.TextEditGroup;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+
+/**
+ * This refactoring extracts a string from a file and replaces it by an Android resource ID
+ * such as R.string.foo.
+ *
+ * There are a number of scenarios, which are not all supported yet. The workflow works as
+ * such:
+ *
+ * - User selects a string in a Java (TODO: or XML file) and invokes
+ * the {@link ExtractStringAction}.
+ *
- The action finds the {@link ICompilationUnit} being edited as well as the current
+ * {@link ITextSelection}. The action creates a new instance of this refactoring as
+ * well as an {@link ExtractStringWizard} and runs the operation.
+ *
- TODO: to support refactoring from an XML file, the action should give the {@link IFile}
+ * and then here we would have to determine whether it's a suitable Android XML file or a
+ * suitable Java file.
+ * TODO: enumerate the exact valid contexts in Android XML files, e.g. attributes in layout
+ * files or text elements (e.g. foo) for values, etc.
+ *
- Step 1 of the refactoring is to check the preliminary conditions. Right now we check
+ * that the java source is not read-only and is in sync. We also try to find a string under
+ * the selection. If this fails, the refactoring is aborted.
+ *
- TODO: Find the string in an XML file based on selection.
+ *
- On success, the wizard is shown, which let the user input the new ID to use.
+ *
- The wizard sets the user input values into this refactoring instance, e.g. the new string
+ * ID, the XML file to update, etc. The wizard does use the utility method
+ * {@link #isResIdDuplicate(String, String)} to check whether the new ID is already defined
+ * in the target XML file.
+ *
- Once Preview or Finish is selected in the wizard, the
+ * {@link #checkFinalConditions(IProgressMonitor)} is called to double-check the user input
+ * and compute the actual changes.
+ *
- When all changes are computed, {@link #createChange(IProgressMonitor)} is invoked.
+ *
+ *
+ * The list of changes are:
+ *
+ * - If the target XML does not exist, create it with the new string ID.
+ *
- If the target XML exists, find the node and add the new string ID right after.
+ * If the node is , it needs to be opened.
+ *
- Create an AST rewriter to edit the source Java file and replace all occurences by the
+ * new computed R.string.foo. Also need to rewrite imports to import R as needed.
+ * If there's already a conflicting R included, we need to insert the FQCN instead.
+ *
- TODO: If the source is an XML file, determine if we need to change an attribute or a
+ * a text element.
+ *
- TODO: Have a pref in the wizard: [x] Change other XML Files
+ *
- TODO: Have a pref in the wizard: [x] Change other Java Files
+ *
+ */
+class ExtractStringRefactoring extends Refactoring {
+
+ /** The compilation unit, a.k.a. the Java file model. */
+ private final ICompilationUnit mUnit;
+ private final int mSelectionStart;
+ private final int mSelectionEnd;
+ /** The actual string selected, after UTF characters have been escaped, good for display. */
+ private String mTokenString;
+ /** Start position of the string token in the source buffer. */
+ private int mTokenStart;
+ /** End position of the string token in the source buffer. */
+ private int mTokenEnd;
+ private String mXmlStringId;
+ private String mTargetXmlFileWsPath;
+ private HashMap> mResIdCache;
+ private XPath mXPath;
+ private ArrayList mChanges;
+
+ public ExtractStringRefactoring(Map arguments)
+ throws NullPointerException {
+ mUnit = (ICompilationUnit) JavaCore.create(arguments.get("CU")); //$NON-NLS-1$
+ mSelectionStart = Integer.parseInt(arguments.get("sel-start")); //$NON-NLS-1$
+ mSelectionEnd = Integer.parseInt(arguments.get("sel-end")); //$NON-NLS-1$
+ mTokenStart = Integer.parseInt(arguments.get("tok-start")); //$NON-NLS-1$
+ mTokenEnd = Integer.parseInt(arguments.get("tok-end")); //$NON-NLS-1$
+ mTokenString = arguments.get("tok-esc"); //$NON-NLS-1$
+ }
+
+ private Map createArgumentMap() {
+ HashMap args = new HashMap();
+ args.put("CU", mUnit.getHandleIdentifier()); //$NON-NLS-1$
+ args.put("sel-start", Integer.toString(mSelectionStart)); //$NON-NLS-1$
+ args.put("sel-end", Integer.toString(mSelectionEnd)); //$NON-NLS-1$
+ args.put("tok-start", Integer.toString(mTokenStart)); //$NON-NLS-1$
+ args.put("tok-end", Integer.toString(mTokenEnd)); //$NON-NLS-1$
+ args.put("tok-esc", mTokenString); //$NON-NLS-1$
+ return args;
+ }
+
+ public ExtractStringRefactoring(ICompilationUnit unit, ITextSelection selection) {
+ mUnit = unit;
+
+ mSelectionStart = selection.getOffset();
+ mSelectionEnd = mSelectionStart + selection.getLength();
+ }
+
+ /**
+ * @see org.eclipse.ltk.core.refactoring.Refactoring#getName()
+ */
+ @Override
+ public String getName() {
+ return "Extract Android String";
+ }
+
+ /**
+ * Gets the actual string selected, after UTF characters have been escaped,
+ * good for display.
+ */
+ public String getTokenString() {
+ return mTokenString;
+ }
+
+ /**
+ * Step 1 of 3 of the refactoring:
+ * Checks that the current selection meets the initial condition before the ExtractString
+ * wizard is shown. The check is supposed to be lightweight and quick. Note that at that
+ * point the wizard has not been created yet.
+ *
+ * Here we scan the source buffer to find the token matching the selection.
+ * The check is successful is a Java string literal is selected, the source is in sync
+ * and is not read-only.
+ *
+ * This is also used to extract the string to be modified, so that we can display it in
+ * the refactoring wizard.
+ *
+ * @see org.eclipse.ltk.core.refactoring.Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)
+ *
+ * @throws CoreException
+ */
+ @Override
+ public RefactoringStatus checkInitialConditions(IProgressMonitor monitor)
+ throws CoreException, OperationCanceledException {
+
+ mTokenString = null;
+ mTokenStart = -1;
+ mTokenEnd = -1;
+
+ RefactoringStatus status = new RefactoringStatus();
+
+ try {
+ monitor.beginTask("Checking preconditions...", 3);
+
+ if (!extraChecks(monitor, status)) {
+ return status;
+ }
+
+ try {
+ IBuffer buffer = mUnit.getBuffer();
+
+ IScanner scanner = ToolFactory.createScanner(
+ false, //tokenizeComments
+ false, //tokenizeWhiteSpace
+ false, //assertMode
+ false //recordLineSeparator
+ );
+ scanner.setSource(buffer.getCharacters());
+ monitor.worked(1);
+
+ for(int token = scanner.getNextToken();
+ token != ITerminalSymbols.TokenNameEOF;
+ token = scanner.getNextToken()) {
+ if (scanner.getCurrentTokenStartPosition() <= mSelectionStart &&
+ scanner.getCurrentTokenEndPosition() >= mSelectionEnd) {
+ // found the token, but only keep of the right type
+ if (token == ITerminalSymbols.TokenNameStringLiteral) {
+ mTokenString = new String(scanner.getCurrentTokenSource());
+ mTokenStart = scanner.getCurrentTokenStartPosition();
+ mTokenEnd = scanner.getCurrentTokenEndPosition();
+ }
+ break;
+ } else if (scanner.getCurrentTokenStartPosition() > mSelectionEnd) {
+ // scanner is past the selection, abort.
+ break;
+ }
+ }
+ } catch (JavaModelException e1) {
+ // Error in mUnit.getBuffer. Ignore.
+ } catch (InvalidInputException e2) {
+ // Error in scanner.getNextToken. Ignore.
+ } finally {
+ monitor.worked(1);
+ }
+
+ if (mTokenString != null) {
+ // As a literal string, the token should have surrounding quotes. Remove them.
+ int len = mTokenString.length();
+ if (len > 0 &&
+ mTokenString.charAt(0) == '"' &&
+ mTokenString.charAt(len - 1) == '"') {
+ mTokenString = mTokenString.substring(1, len - 1);
+ }
+ // We need a non-empty string literal
+ if (mTokenString.length() == 0) {
+ mTokenString = null;
+ }
+ }
+
+ if (mTokenString == null) {
+ status.addFatalError("Please select a Java string literal.");
+ }
+
+ monitor.worked(1);
+ } finally {
+ monitor.done();
+ }
+
+ return status;
+ }
+
+ /**
+ * Tests from org.eclipse.jdt.internal.corext.refactoringChecks#validateEdit()
+ * Might not be useful.
+ *
+ * @return False if caller should abort, true if caller should continue.
+ */
+ private boolean extraChecks(IProgressMonitor monitor, RefactoringStatus status) {
+ //
+ IResource res = mUnit.getPrimary().getResource();
+ if (res == null || res.getType() != IResource.FILE) {
+ status.addFatalError("Cannot access resource; only regular files can be used.");
+ return false;
+ }
+ monitor.worked(1);
+
+ // check whether the source file is in sync
+ if (!res.isSynchronized(IResource.DEPTH_ZERO)) {
+ status.addFatalError("The file is not synchronized. Please save it first.");
+ return false;
+ }
+ monitor.worked(1);
+
+ // make sure we can write to it.
+ ResourceAttributes resAttr = res.getResourceAttributes();
+ if (mUnit.isReadOnly() || resAttr == null || resAttr.isReadOnly()) {
+ status.addFatalError("The file is read-only, please make it writeable first.");
+ return false;
+ }
+ monitor.worked(1);
+
+ return true;
+ }
+
+ /**
+ * Step 2 of 3 of the refactoring:
+ * Check the conditions once the user filled values in the refactoring wizard,
+ * then prepare the changes to be applied.
+ *
+ * In this case, most of the sanity checks are done by the wizard so essentially this
+ * should only be called if the wizard positively validated the user input.
+ *
+ * Here we do check that the target resource XML file either does not exists or
+ * is not read-only.
+ *
+ * @see org.eclipse.ltk.core.refactoring.Refactoring#checkFinalConditions(IProgressMonitor)
+ *
+ * @throws CoreException
+ */
+ @Override
+ public RefactoringStatus checkFinalConditions(IProgressMonitor monitor)
+ throws CoreException, OperationCanceledException {
+ RefactoringStatus status = new RefactoringStatus();
+
+ try {
+ monitor.beginTask("Checking post-conditions...", 3);
+
+ if (mXmlStringId == null || mXmlStringId.length() <= 0) {
+ // this is not supposed to happen
+ status.addFatalError("Missing replacement string ID");
+ } else if (mTargetXmlFileWsPath == null || mTargetXmlFileWsPath.length() <= 0) {
+ // this is not supposed to happen
+ status.addFatalError("Missing target xml file path");
+ }
+ monitor.worked(1);
+
+ // Either that resource must not exist or it must be a writeable file.
+ IResource targetXml = getTargetXmlResource(mTargetXmlFileWsPath);
+ if (targetXml != null) {
+ if (targetXml.getType() != IResource.FILE) {
+ status.addFatalError(
+ String.format("XML file '%1$s' is not a file.", mTargetXmlFileWsPath));
+ } else {
+ ResourceAttributes attr = targetXml.getResourceAttributes();
+ if (attr != null && attr.isReadOnly()) {
+ status.addFatalError(
+ String.format("XML file '%1$s' is read-only.",
+ mTargetXmlFileWsPath));
+ }
+ }
+ }
+ monitor.worked(1);
+
+ if (status.hasError()) {
+ return status;
+ }
+
+ mChanges = new ArrayList();
+
+
+ // Prepare the change for the XML file.
+
+ if (!isResIdDuplicate(mTargetXmlFileWsPath, mXmlStringId)) {
+ // We actually change it only if the ID doesn't exist yet
+ TextFileChange xmlChange = new TextFileChange(getName(), (IFile) targetXml);
+ xmlChange.setTextType("xml"); //$NON-NLS-1$
+ TextEdit edit = createXmlEdit((IFile) targetXml, mXmlStringId, mTokenString);
+ if (edit == null) {
+ status.addFatalError(String.format("Failed to modify file %1$s",
+ mTargetXmlFileWsPath));
+ }
+ xmlChange.setEdit(edit);
+ mChanges.add(xmlChange);
+ }
+ monitor.worked(1);
+
+ if (status.hasError()) {
+ return status;
+ }
+
+ // Prepare the change to the Java compilation unit
+ List changes = computeJavaChanges(mUnit, mXmlStringId, mTokenString, status,
+ SubMonitor.convert(monitor, 1));
+ if (changes != null) {
+ mChanges.addAll(changes);
+ }
+
+ monitor.worked(1);
+ } finally {
+ monitor.done();
+ }
+
+ return status;
+ }
+
+ /**
+ * Internal helper that actually prepares the {@link TextEdit} that adds the given
+ * ID to the given XML File.
+ *
+ * This does not actually modify the file.
+ *
+ * @param xmlFile The file resource to modify.
+ * @param replacementStringId The new ID to insert.
+ * @param oldString The old string, which will be the value in the XML string.
+ * @return A new {@link TextEdit} that describes how to change the file.
+ */
+ private TextEdit createXmlEdit(IFile xmlFile, String replacementStringId, String oldString) {
+
+ if (!xmlFile.exists()) {
+ // The XML file does not exist. Simply create it.
+ StringBuilder content = new StringBuilder();
+ content.append("\n"); //$NON-NLS-1$
+ content.append("\n"); //$NON-NLS-1$
+ content.append(" "). //$NON-NLS-1$
+ append(oldString).
+ append("\n"); //$NON-NLS-1$
+ content.append("\n"); //$NON-NLS-1$
+
+ return new InsertEdit(0, content.toString());
+ }
+
+ // The file exist. Attempt to parse it as a valid XML document.
+ try {
+ int[] indices = new int[2];
+ if (findXmlOpeningTagPos(xmlFile.getContents(), "resources", indices)) { //$NON-NLS-1$
+ // Indices[1] indicates whether we found > or />. It can only be 1 or 2.
+ // Indices[0] is the position of the first character of either > or />.
+ //
+ // Note: we don't even try to adapt our formatting to the existing structure (we
+ // could by capturing whatever whitespace is after the closing bracket and
+ // applying it here before our tag, unless we were dealing with an empty
+ // resource tag.)
+
+ int offset = indices[0];
+ int len = indices[1];
+ StringBuilder content = new StringBuilder();
+ content.append(">\n"); //$NON-NLS-1$
+ content.append(" "). //$NON-NLS-1$
+ append(oldString).
+ append(""); //$NON-NLS-1$
+ if (len == 2) {
+ content.append("\n"); //$NON-NLS-1$
+ }
+
+ return new ReplaceEdit(offset, len, content.toString());
+ }
+
+ } catch (CoreException e) {
+ // Failed to read file. Ignore. Will return null below.
+ }
+
+ return null;
+ }
+
+ /**
+ * Parse an XML input stream, looking for an opening tag.
+ *
+ * If found, returns the character offet in the buffer of the closing bracket of that
+ * tag, e.g. the position of > in "". The first character is at offset 0.
+ *
+ * The implementation here relies on a simple character-based parser. No DOM nor SAX
+ * parsing is used, due to the simplified nature of the task: we just want the first
+ * opening tag, which in our case should be the document root. We deal however with
+ * with the tag being commented out, so comments are skipped. We assume the XML doc
+ * is sane, e.g. we don't expect the tag to appear in the middle of a string. But
+ * again since in fact we want the root element, that's unlikely to happen.
+ *
+ * We need to deal with the case where the element is written as , in
+ * which case the caller will want to replace /> by ">...". To do that we return
+ * two values: the first offset of the closing tag (e.g. / or >) and the length, which
+ * can only be 1 or 2. If it's 2, the caller have to deal with /> instead of just >.
+ *
+ * @param contents An existing buffer to parse.
+ * @param tag The tag to look for.
+ * @param indices The return values: [0] is the offset of the closing bracket and [1] is
+ * the length which can be only 1 for > and 2 for />
+ * @return True if we found the tag, in which case indices can be used.
+ */
+ private boolean findXmlOpeningTagPos(InputStream contents, String tag, int[] indices) {
+
+ BufferedReader br = new BufferedReader(new InputStreamReader(contents));
+ StringBuilder sb = new StringBuilder(); // scratch area
+
+ tag = "<" + tag;
+ int tagLen = tag.length();
+ int maxLen = tagLen < 3 ? 3 : tagLen;
+
+ try {
+ int offset = 0;
+ int i = 0;
+ char searching = '<'; // we want opening tags
+ boolean capture = false;
+ boolean inComment = false;
+ boolean inTag = false;
+ while ((i = br.read()) != -1) {
+ char c = (char) i;
+ if (c == searching) {
+ capture = true;
+ }
+ if (capture) {
+ sb.append(c);
+ int len = sb.length();
+ if (inComment && c == '>') {
+ // is the comment being closed?
+ if (len >= 3 && sb.substring(len-3).equals("-->")) { //$NON-NLS-1$
+ // yes, comment is closing, stop capturing
+ capture = false;
+ inComment = false;
+ sb.setLength(0);
+ }
+ } else if (inTag && c == '>') {
+ // we're capturing in our tag, waiting for the closing >, we just got it
+ // so we're totally done here. Simply detect whether it's /> or >.
+ indices[0] = offset;
+ indices[1] = 1;
+ if (sb.charAt(len - 2) == '/') {
+ indices[0]--;
+ indices[1]++;
+ }
+ return true;
+
+ } else if (!inComment && !inTag) {
+ // not a comment and not our tag yet, so we're capturing because a
+ // tag is being opened but we don't know which one yet.
+
+ // look for either the opening or a comment or
+ // the opening of our tag.
+ if (len == 3 && sb.equals("<--")) { //$NON-NLS-1$
+ inComment = true;
+ } else if (len == tagLen && sb.toString().equals(tag)) {
+ inTag = true;
+ }
+
+ // if we're not interested in this tag yet, deal with when to stop
+ // capturing: the opening tag ends with either any kind of whitespace
+ // or with a > or maybe there's a PI that starts with
+ if (!inComment && !inTag) {
+ if (c == '>' || c == '?' || c == ' ' || c == '\n' || c == '\r') {
+ // stop capturing
+ capture = false;
+ sb.setLength(0);
+ }
+ }
+ }
+
+ if (capture && len > maxLen) {
+ // in any case we don't need to capture more than the size of our tag
+ // or the comment opening tag
+ sb.deleteCharAt(0);
+ }
+ }
+ offset++;
+ }
+ } catch (IOException e) {
+ // Ignore.
+ } finally {
+ try {
+ br.close();
+ } catch (IOException e) {
+ // oh come on...
+ }
+ }
+
+ return false;
+ }
+
+ private List computeJavaChanges(ICompilationUnit unit,
+ String xmlStringId,
+ String tokenString,
+ RefactoringStatus status,
+ SubMonitor subMonitor) {
+
+ // Get the Android package name from the Android Manifest. We need it to create
+ // the FQCN of the R class.
+ String packageName = null;
+ String error = null;
+ IProject proj = unit.getJavaProject().getProject();
+ IResource manifestFile = proj.findMember(AndroidConstants.FN_ANDROID_MANIFEST);
+ if (manifestFile == null || manifestFile.getType() != IResource.FILE) {
+ error = "File not found";
+ } else {
+ try {
+ AndroidManifestParser manifest = AndroidManifestParser.parseForData(
+ (IFile) manifestFile);
+ if (manifest == null) {
+ error = "Invalid content";
+ } else {
+ packageName = manifest.getPackage();
+ if (packageName == null) {
+ error = "Missing package definition";
+ }
+ }
+ } catch (CoreException e) {
+ error = e.getLocalizedMessage();
+ }
+ }
+
+ if (error != null) {
+ status.addFatalError(
+ String.format("Failed to parse file %1$s: %2$s.",
+ manifestFile.getFullPath(), error));
+ return null;
+ }
+
+ // TODO in a future version we might want to collect various Java files that
+ // need to be updated in the same project and process them all together.
+ // To do that we need to use an ASTRequestor and parser.createASTs, kind of
+ // like this:
+ //
+ // ASTRequestor requestor = new ASTRequestor() {
+ // @Override
+ // public void acceptAST(ICompilationUnit sourceUnit, CompilationUnit astNode) {
+ // super.acceptAST(sourceUnit, astNode);
+ // // TODO process astNode
+ // }
+ // };
+ // ...
+ // parser.createASTs(compilationUnits, bindingKeys, requestor, monitor)
+ //
+ // and then add multiple TextFileChange to the changes arraylist.
+
+ // Right now the changes array will contain one TextFileChange at most.
+ ArrayList changes = new ArrayList();
+
+ // This is the unit that will be modified.
+ TextFileChange change = new TextFileChange(getName(), (IFile) unit.getResource());
+ change.setTextType("java"); //$NON-NLS-1$
+
+ // Create an AST for this compilation unit
+ ASTParser parser = ASTParser.newParser(AST.JLS3);
+ parser.setProject(unit.getJavaProject());
+ parser.setSource(unit);
+ parser.setResolveBindings(true);
+ ASTNode node = parser.createAST(subMonitor.newChild(1));
+
+ // The ASTNode must be a CompilationUnit, by design
+ if (!(node instanceof CompilationUnit)) {
+ status.addFatalError(String.format("Internal error: ASTNode class %s", //$NON-NLS-1$
+ node.getClass()));
+ return null;
+ }
+
+ // ImportRewrite will allow us to add the new type to the imports and will resolve
+ // what the Java source must reference, e.g. the FQCN or just the simple name.
+ ImportRewrite ir = ImportRewrite.create((CompilationUnit) node, true);
+ String Rqualifier = packageName + ".R"; //$NON-NLS-1$
+ Rqualifier = ir.addImport(Rqualifier);
+
+ // Rewrite the AST itself via an ASTVisitor
+ AST ast = node.getAST();
+ ASTRewrite ar = ASTRewrite.create(ast);
+ ReplaceStringsVisitor visitor = new ReplaceStringsVisitor(ast, ar,
+ tokenString, Rqualifier, xmlStringId);
+ node.accept(visitor);
+
+ // Finally prepare the change set
+ try {
+ MultiTextEdit edit = new MultiTextEdit();
+
+ // Create the edit to change the imports, only if anything changed
+ TextEdit subEdit = ir.rewriteImports(subMonitor.newChild(1));
+ if (subEdit.hasChildren()) {
+ edit.addChild(subEdit);
+ }
+
+ // Create the edit to change the Java source, only if anything changed
+ subEdit = ar.rewriteAST();
+ if (subEdit.hasChildren()) {
+ edit.addChild(subEdit);
+ }
+
+ // Only create a change set if any edit was collected
+ if (edit.hasChildren()) {
+ change.setEdit(edit);
+ changes.add(change);
+ }
+
+ // TODO to modify another Java source, loop back to the creation of the
+ // TextFileChange and accumulate in changes. Right now only one source is
+ // modified.
+
+ if (changes.size() > 0) {
+ return changes;
+ }
+
+ } catch (CoreException e) {
+ // ImportRewrite.rewriteImports failed.
+ status.addFatalError(e.getMessage());
+ }
+ return null;
+ }
+
+ public class ReplaceStringsVisitor extends ASTVisitor {
+
+ private final AST mAst;
+ private final ASTRewrite mRewriter;
+ private final String mOldString;
+ private final String mRQualifier;
+ private final String mXmlId;
+
+ public ReplaceStringsVisitor(AST ast,
+ ASTRewrite astRewrite,
+ String oldString,
+ String rQualifier,
+ String xmlId) {
+ mAst = ast;
+ mRewriter = astRewrite;
+ mOldString = oldString;
+ mRQualifier = rQualifier;
+ mXmlId = xmlId;
+ }
+
+ @Override
+ public boolean visit(StringLiteral node) {
+ if (node.getLiteralValue().equals(mOldString)) {
+
+ Name qualifierName = mAst.newName(mRQualifier + ".string"); //$NON-NLS-1$
+ SimpleName idName = mAst.newSimpleName(mXmlId);
+ QualifiedName newNode = mAst.newQualifiedName(qualifierName, idName);
+
+ TextEditGroup editGroup = new TextEditGroup(getName());
+ mRewriter.replace(node, newNode, editGroup);
+ }
+ return super.visit(node);
+ }
+ }
+
+ /**
+ * Step 3 of 3 of the refactoring: returns the {@link Change} that will be able to do the
+ * work and creates a descriptor that can be used to replay that refactoring later.
+ *
+ * @see org.eclipse.ltk.core.refactoring.Refactoring#createChange(org.eclipse.core.runtime.IProgressMonitor)
+ *
+ * @throws CoreException
+ */
+ @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() {
+
+ String comment = String.format(
+ "Extracts string '%1$s' into R.string.%2$s",
+ mTokenString,
+ mXmlStringId);
+
+ ExtractStringDescriptor desc = new ExtractStringDescriptor(
+ mUnit.getJavaProject().getElementName(), //project
+ comment, //description
+ comment, //comment
+ createArgumentMap());
+
+ return new RefactoringChangeDescriptor(desc);
+ }
+ };
+
+ monitor.worked(1);
+
+ return change;
+
+ } finally {
+ monitor.done();
+ }
+
+ }
+
+ /**
+ * Utility method used by the wizard to check whether the given string ID is already
+ * defined in the XML file which path is given.
+ *
+ * @param xmlFileWsPath The project path of the XML file, e.g. "/res/values/strings.xml".
+ * The given file may or may not exist.
+ * @param stringId The string ID to find.
+ * @return True if such a string ID is already defined.
+ */
+ public boolean isResIdDuplicate(String xmlFileWsPath, String stringId) {
+ // This is going to be called many times on the same file.
+ // Build a cache of the existing IDs for a given file.
+ if (mResIdCache == null) {
+ mResIdCache = new HashMap>();
+ }
+ HashSet cache = mResIdCache.get(xmlFileWsPath);
+ if (cache == null) {
+ cache = getResIdsForFile(xmlFileWsPath);
+ mResIdCache.put(xmlFileWsPath, cache);
+ }
+
+ return cache.contains(stringId);
+ }
+
+ /**
+ * Extract all the defined string IDs from a given file using XPath.
+ *
+ * @param xmlFileWsPath The project path of the file to parse. It may not exist.
+ * @return The set of all string IDs defined in the file. The returned set is always non
+ * null. It is empty if the file does not exist.
+ */
+ private HashSet getResIdsForFile(String xmlFileWsPath) {
+ HashSet ids = new HashSet();
+
+ if (mXPath == null) {
+ mXPath = AndroidXPathFactory.newXPath();
+ }
+
+ // Access the project that contains the resource that contains the compilation unit
+ IResource resource = getTargetXmlResource(xmlFileWsPath);
+
+ if (resource != null && resource.exists() && resource.getType() == IResource.FILE) {
+ InputSource source;
+ try {
+ source = new InputSource(((IFile) resource).getContents());
+
+ // We want all the IDs in an XML structure like this:
+ //
+ // something
+ //
+
+ String xpathExpr = "/resources/string/@name"; //$NON-NLS-1$
+
+ Object result = mXPath.evaluate(xpathExpr, source, XPathConstants.NODESET);
+ if (result instanceof NodeList) {
+ NodeList list = (NodeList) result;
+ for (int n = list.getLength() - 1; n >= 0; n--) {
+ String id = list.item(n).getNodeValue();
+ ids.add(id);
+ }
+ }
+
+ } catch (CoreException e1) {
+ // IFile.getContents failed. Ignore.
+ } catch (XPathExpressionException e) {
+ // mXPath.evaluate failed. Ignore.
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * Given a file project path, returns its resource in the same project than the
+ * compilation unit. The resource may not exist.
+ */
+ private IResource getTargetXmlResource(String xmlFileWsPath) {
+ IProject proj = mUnit.getPrimary().getResource().getProject();
+ Path path = new Path(xmlFileWsPath);
+ IResource resource = proj.findMember(path);
+ return resource;
+ }
+
+ /**
+ * Sets the replacement string ID. Used by the wizard to set the user input.
+ */
+ public void setReplacementStringId(String replacementStringId) {
+ mXmlStringId = replacementStringId;
+ }
+
+ /**
+ * Sets the target file. This is a project path, e.g. "/res/values/strings.xml".
+ * Used by the wizard to set the user input.
+ */
+ public void setTargetFile(String targetXmlFileWsPath) {
+ mTargetXmlFileWsPath = targetXmlFileWsPath;
+ }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringWizard.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringWizard.java
new file mode 100644
index 000000000..2083a6e5d
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/refactorings/extractstring/ExtractStringWizard.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2009 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.refactorings.extractstring;
+
+import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
+
+/**
+ * A wizard for ExtractString based on a simple dialog with one page.
+ *
+ * @see ExtractStringInputPage
+ * @see ExtractStringRefactoring
+ */
+class ExtractStringWizard extends RefactoringWizard {
+
+ /**
+ * Create a wizard for ExtractString based on a simple dialog with one page.
+ */
+ public ExtractStringWizard(ExtractStringRefactoring ref, String title) {
+ super(ref, DIALOG_BASED_USER_INTERFACE | PREVIEW_EXPAND_FIRST_NODE);
+ setDefaultPageTitle(title);
+ }
+
+ @Override
+ protected void addUserInputPages() {
+ addPage(new ExtractStringInputPage());
+ }
+
+}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java
index 5abfd811d..1da753c7b 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java
@@ -180,6 +180,8 @@ public class AndroidConstants {
public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
+ public final static String CLASS_INSTRUMENTATION_RUNNER =
+ "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
public final static String CLASS_R = "android.R"; //$NON-NLS-1$
public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
@@ -215,4 +217,5 @@ public class AndroidConstants {
/** The base URL where to find the Android class & manifest documentation */
public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$
+ public static final String LIBRARY_TEST_RUNNER = "android.test.runner"; // $NON-NLS-1$
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
index 42c881b15..fa7e9b9f4 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.common.project;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
import com.android.sdklib.SdkConstants;
@@ -255,12 +256,8 @@ public class AndroidManifestParser {
} catch (NumberFormatException e) {
handleError(e, -1 /* lineNumber */);
}
- } else if (NODE_INSTRUMENTATION.equals(localName)) {
- value = getAttributeValue(attributes, ATTRIBUTE_NAME,
- true /* hasNamespace */);
- if (value != null) {
- mInstrumentations.add(value);
- }
+ } else if (NODE_INSTRUMENTATION.equals(localName)) {
+ processInstrumentationNode(attributes);
}
break;
case LEVEL_ACTIVITY:
@@ -449,6 +446,25 @@ public class AndroidManifestParser {
addProcessName(processName);
}
}
+
+ /**
+ * Processes the instrumentation nodes.
+ * @param attributes the attributes for the activity node.
+ * node is representing
+ */
+ private void processInstrumentationNode(Attributes attributes) {
+ // lets get the class name, and check it if required.
+ String instrumentationName = getAttributeValue(attributes, ATTRIBUTE_NAME,
+ true /* hasNamespace */);
+ if (instrumentationName != null) {
+ String instrClassName = combinePackageAndClassName(mPackage, instrumentationName);
+ mInstrumentations.add(instrClassName);
+ if (mMarkErrors) {
+ checkClass(instrClassName, AndroidConstants.CLASS_INSTRUMENTATION,
+ true /* testVisibility */);
+ }
+ }
+ }
/**
* Checks that a class is valid and can be used in the Android Manifest.
@@ -484,8 +500,7 @@ public class AndroidManifestParser {
} catch (CoreException e) {
}
}
- }
-
+ }
}
/**
@@ -565,7 +580,6 @@ public class AndroidManifestParser {
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
errorListener, gatherData, javaProject, markErrors);
-
parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
// get the result from the handler
@@ -576,14 +590,19 @@ public class AndroidManifestParser {
manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(),
manifestHandler.getUsesLibraries());
} catch (ParserConfigurationException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "Bad parser configuration for %s", manifestFile.getFullPath());
} catch (SAXException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "Parser exception for %s", manifestFile.getFullPath());
} catch (IOException e) {
- } finally {
- }
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "I/O error for %s", manifestFile.getFullPath());
+ }
return null;
}
-
+
/**
* Parses the Android Manifest, and returns an object containing the result of the parsing.
*
@@ -619,11 +638,15 @@ public class AndroidManifestParser {
manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(),
manifestHandler.getUsesLibraries());
} catch (ParserConfigurationException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "Bad parser configuration for %s", manifestFile.getAbsolutePath());
} catch (SAXException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "Parser exception for %s", manifestFile.getAbsolutePath());
} catch (IOException e) {
- } finally {
- }
-
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "I/O error for %s", manifestFile.getAbsolutePath());
+ }
return null;
}
@@ -646,10 +669,12 @@ public class AndroidManifestParser {
boolean gatherData,
boolean markErrors)
throws CoreException {
+
+ IFile manifestFile = getManifest(javaProject.getProject());
+
try {
SAXParser parser = sParserFactory.newSAXParser();
-
- IFile manifestFile = getManifest(javaProject.getProject());
+
if (manifestFile != null) {
ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
errorListener, gatherData, javaProject, markErrors);
@@ -664,10 +689,15 @@ public class AndroidManifestParser {
manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries());
}
} catch (ParserConfigurationException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "Bad parser configuration for %s", manifestFile.getFullPath());
} catch (SAXException e) {
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "Parser exception for %s", manifestFile.getFullPath());
} catch (IOException e) {
- } finally {
- }
+ AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
+ "I/O error for %s", manifestFile.getFullPath());
+ }
return null;
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java
index 9c529e5da..12d49fe27 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java
@@ -61,8 +61,10 @@ import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.LanguageReg
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.MobileCodeVerifier;
import com.android.layoutlib.api.ILayoutLog;
import com.android.layoutlib.api.ILayoutResult;
+import com.android.layoutlib.api.IProjectCallback;
import com.android.layoutlib.api.IResourceValue;
import com.android.layoutlib.api.IStyleResourceValue;
+import com.android.layoutlib.api.IXmlPullParser;
import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
import com.android.sdklib.IAndroidTarget;
@@ -222,7 +224,7 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
// updateUiFromFramework will reset language/region combo, so we must call
// setConfiguration after, or the settext on language/region will be lost.
if (mEditedConfig != null) {
- setConfiguration(mEditedConfig);
+ setConfiguration(mEditedConfig, false /*force*/);
}
// make sure we remove the custom view loader, since its parent class loader is the
@@ -867,7 +869,7 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
@Override
void editNewFile(FolderConfiguration configuration) {
// update the configuration UI
- setConfiguration(configuration);
+ setConfiguration(configuration, true /*force*/);
// enable the create button if the current and edited config are not equals
mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
@@ -975,18 +977,14 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
int themeIndex = mThemeCombo.getSelectionIndex();
if (themeIndex != -1) {
String theme = mThemeCombo.getItem(themeIndex);
-
- // change the string if it's a custom theme to make sure we can
- // differentiate them
- if (themeIndex >= mPlatformThemeCount) {
- theme = "*" + theme; //$NON-NLS-1$
- }
// Render a single object as described by the ViewElementDescriptor.
WidgetPullParser parser = new WidgetPullParser(descriptor);
- ILayoutResult result = bridge.bridge.computeLayout(parser,
+ ILayoutResult result = computeLayout(bridge, parser,
null /* projectKey */,
- 300 /* width */, 300 /* height */, theme,
+ 300 /* width */, 300 /* height */, 160 /*density*/,
+ 160.f /*xdpi*/, 160.f /*ydpi*/, theme,
+ themeIndex >= mPlatformThemeCount /*isProjectTheme*/,
configuredProjectResources, frameworkResources, projectCallback,
null /* logger */);
@@ -1073,11 +1071,14 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
/**
* Update the UI controls state with a given {@link FolderConfiguration}.
- * If a qualifier is not present in the {@link FolderConfiguration} object, the UI control
- * is not modified. However if the value in the control is not the default value, a warning
- * icon is showed.
+ * If force is set to true the UI will be changed to exactly reflect
+ * config, otherwise, if a qualifier is not present in config,
+ * the UI control is not modified. However if the value in the control is not the default value,
+ * a warning icon is shown.
+ * @param config The {@link FolderConfiguration} to set.
+ * @param force Whether the UI should be changed to exactly match the received configuration.
*/
- void setConfiguration(FolderConfiguration config) {
+ void setConfiguration(FolderConfiguration config, boolean force) {
mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets.
mEditedConfig = config;
@@ -1088,6 +1089,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (countryQualifier != null) {
mCountry.setText(String.format("%1$d", countryQualifier.getCode()));
mCurrentConfig.setCountryCodeQualifier(countryQualifier);
+ } else if (force) {
+ mCountry.setText(""); //$NON-NLS-1$
+ mCurrentConfig.setCountryCodeQualifier(null);
} else if (mCountry.getText().length() > 0) {
mCountryIcon.setImage(mWarningImage);
}
@@ -1097,6 +1101,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (networkQualifier != null) {
mNetwork.setText(String.format("%1$d", networkQualifier.getCode()));
mCurrentConfig.setNetworkCodeQualifier(networkQualifier);
+ } else if (force) {
+ mNetwork.setText(""); //$NON-NLS-1$
+ mCurrentConfig.setNetworkCodeQualifier(null);
} else if (mNetwork.getText().length() > 0) {
mNetworkIcon.setImage(mWarningImage);
}
@@ -1106,6 +1113,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (languageQualifier != null) {
mLanguage.setText(languageQualifier.getValue());
mCurrentConfig.setLanguageQualifier(languageQualifier);
+ } else if (force) {
+ mLanguage.setText(""); //$NON-NLS-1$
+ mCurrentConfig.setLanguageQualifier(null);
} else if (mLanguage.getText().length() > 0) {
mLanguageIcon.setImage(mWarningImage);
}
@@ -1115,6 +1125,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (regionQualifier != null) {
mRegion.setText(regionQualifier.getValue());
mCurrentConfig.setRegionQualifier(regionQualifier);
+ } else if (force) {
+ mRegion.setText(""); //$NON-NLS-1$
+ mCurrentConfig.setRegionQualifier(null);
} else if (mRegion.getText().length() > 0) {
mRegionIcon.setImage(mWarningImage);
}
@@ -1125,6 +1138,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
mOrientation.select(
ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1);
mCurrentConfig.setScreenOrientationQualifier(orientationQualifier);
+ } else if (force) {
+ mOrientation.select(0);
+ mCurrentConfig.setScreenOrientationQualifier(null);
} else if (mOrientation.getSelectionIndex() != 0) {
mOrientationIcon.setImage(mWarningImage);
}
@@ -1134,6 +1150,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (densityQualifier != null) {
mDensity.setText(String.format("%1$d", densityQualifier.getValue()));
mCurrentConfig.setPixelDensityQualifier(densityQualifier);
+ } else if (force) {
+ mDensity.setText(""); //$NON-NLS-1$
+ mCurrentConfig.setPixelDensityQualifier(null);
} else if (mDensity.getText().length() > 0) {
mDensityIcon.setImage(mWarningImage);
}
@@ -1143,6 +1162,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (touchQualifier != null) {
mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1);
mCurrentConfig.setTouchTypeQualifier(touchQualifier);
+ } else if (force) {
+ mTouch.select(0);
+ mCurrentConfig.setTouchTypeQualifier(null);
} else if (mTouch.getSelectionIndex() != 0) {
mTouchIcon.setImage(mWarningImage);
}
@@ -1152,6 +1174,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (keyboardQualifier != null) {
mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1);
mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier);
+ } else if (force) {
+ mKeyboard.select(0);
+ mCurrentConfig.setKeyboardStateQualifier(null);
} else if (mKeyboard.getSelectionIndex() != 0) {
mKeyboardIcon.setImage(mWarningImage);
}
@@ -1161,6 +1186,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (inputQualifier != null) {
mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1);
mCurrentConfig.setTextInputMethodQualifier(inputQualifier);
+ } else if (force) {
+ mTextInput.select(0);
+ mCurrentConfig.setTextInputMethodQualifier(null);
} else if (mTextInput.getSelectionIndex() != 0) {
mTextInputIcon.setImage(mWarningImage);
}
@@ -1171,6 +1199,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
mNavigation.select(
NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1);
mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter);
+ } else if (force) {
+ mNavigation.select(0);
+ mCurrentConfig.setNavigationMethodQualifier(null);
} else if (mNavigation.getSelectionIndex() != 0) {
mNavigationIcon.setImage(mWarningImage);
}
@@ -1181,6 +1212,10 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
mSize1.setText(String.format("%1$d", sizeQualifier.getValue1()));
mSize2.setText(String.format("%1$d", sizeQualifier.getValue2()));
mCurrentConfig.setScreenDimensionQualifier(sizeQualifier);
+ } else if (force) {
+ mSize1.setText(""); //$NON-NLS-1$
+ mSize2.setText(""); //$NON-NLS-1$
+ mCurrentConfig.setScreenDimensionQualifier(null);
} else if (mSize1.getText().length() > 0 && mSize2.getText().length() > 0) {
mSizeIcon.setImage(mWarningImage);
}
@@ -1607,7 +1642,7 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
// at this point, we have not opened a new file.
// update the configuration icons with the new edited config.
- setConfiguration(mEditedConfig);
+ setConfiguration(mEditedConfig, false /*force*/);
// enable the create button if the current and edited config are not equals
mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
@@ -1794,45 +1829,16 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
// Compute the layout
UiElementPullParser parser = new UiElementPullParser(getModel());
Rectangle rect = getBounds();
- ILayoutResult result = null;
- if (bridge.apiLevel >= 3) {
- // call the new api with proper theme differentiator and
- // density/dpi support.
- boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
+ boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
- // FIXME pass the density/dpi from somewhere (resource config or skin).
- result = bridge.bridge.computeLayout(parser,
- iProject /* projectKey */,
- rect.width, rect.height, 160, 160.f, 160.f,
- theme, isProjectTheme,
- mConfiguredProjectRes, frameworkResources, mProjectCallback,
- mLogger);
- } else if (bridge.apiLevel == 2) {
- // api with boolean for separation of project/framework theme
- boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
+ // FIXME pass the density/dpi from somewhere (resource config or skin).
+ ILayoutResult result = computeLayout(bridge, parser,
+ iProject /* projectKey */,
+ rect.width, rect.height, 160, 160.f, 160.f,
+ theme, isProjectTheme,
+ mConfiguredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
- result = bridge.bridge.computeLayout(parser,
- iProject /* projectKey */,
- rect.width, rect.height, theme, isProjectTheme,
- mConfiguredProjectRes, frameworkResources, mProjectCallback,
- mLogger);
- } else {
- // oldest api with no density/dpi, and project theme boolean mixed
- // into the theme name.
-
- // change the string if it's a custom theme to make sure we can
- // differentiate them
- if (themeIndex >= mPlatformThemeCount) {
- theme = "*" + theme; //$NON-NLS-1$
- }
-
- result = bridge.bridge.computeLayout(parser,
- iProject /* projectKey */,
- rect.width, rect.height, theme,
- mConfiguredProjectRes, frameworkResources, mProjectCallback,
- mLogger);
- }
-
// update the UiElementNode with the layout info.
if (result.getSuccess() == ILayoutResult.SUCCESS) {
model.setEditData(result.getImage());
@@ -2383,4 +2389,48 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
return null;
}
+
+ /**
+ * Computes a layout by calling the correct computeLayout method of ILayoutBridge based on
+ * the implementation API level.
+ */
+ @SuppressWarnings("deprecation")
+ private ILayoutResult computeLayout(LayoutBridge bridge,
+ IXmlPullParser layoutDescription, Object projectKey,
+ int screenWidth, int screenHeight, int density, float xdpi, float ydpi,
+ String themeName, boolean isProjectTheme,
+ Map> projectResources,
+ Map> frameworkResources,
+ IProjectCallback projectCallback, ILayoutLog logger) {
+
+ if (bridge.apiLevel >= 3) {
+ // newer api with boolean for separation of project/framework theme,
+ // and density support.
+ return bridge.bridge.computeLayout(layoutDescription,
+ projectKey, screenWidth, screenHeight, density, xdpi, ydpi,
+ themeName, isProjectTheme,
+ projectResources, frameworkResources, projectCallback,
+ logger);
+ } else if (bridge.apiLevel == 2) {
+ // api with boolean for separation of project/framework theme
+ return bridge.bridge.computeLayout(layoutDescription,
+ projectKey, screenWidth, screenHeight, themeName, isProjectTheme,
+ mConfiguredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
+ } else {
+ // oldest api with no density/dpi, and project theme boolean mixed
+ // into the theme name.
+
+ // change the string if it's a custom theme to make sure we can
+ // differentiate them
+ if (isProjectTheme) {
+ themeName = "*" + themeName; //$NON-NLS-1$
+ }
+
+ return bridge.bridge.computeLayout(layoutDescription,
+ projectKey, screenWidth, screenHeight, themeName,
+ mConfiguredProjectRes, frameworkResources, mProjectCallback,
+ mLogger);
+ }
+ }
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java
index 262ef65a1..d86d585a3 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/AdtTestData.java
@@ -15,7 +15,13 @@
*/
package com.android.ide.eclipse.tests;
+import com.android.ide.eclipse.common.AndroidConstants;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.Platform;
+
import java.io.File;
+import java.io.IOException;
import java.net.URL;
import java.util.logging.Logger;
@@ -45,12 +51,29 @@ public class AdtTestData {
// accessed normally
mOsRootDataPath = System.getProperty("test_data");
if (mOsRootDataPath == null) {
- sLogger.info("Cannot find test_data directory, init to class loader");
+ sLogger.info("Cannot find test_data environment variable, init to class loader");
URL url = this.getClass().getClassLoader().getResource("data"); //$NON-NLS-1$
- mOsRootDataPath = url.getFile();
+
+ if (Platform.isRunning()) {
+ sLogger.info("Running as an Eclipse Plug-in JUnit test, using FileLocator");
+ try {
+ mOsRootDataPath = FileLocator.resolve(url).getFile();
+ } catch (IOException e) {
+ sLogger.warning("IOException while using FileLocator, reverting to url");
+ mOsRootDataPath = url.getFile();
+ }
+ } else {
+ sLogger.info("Running as an plain JUnit test, using url as-is");
+ mOsRootDataPath = url.getFile();
+ }
}
+
+ if (mOsRootDataPath.equals(AndroidConstants.WS_SEP + "data")) {
+ sLogger.warning("Resource data not found using class loader!, Defaulting to no path");
+ }
+
if (!mOsRootDataPath.endsWith(File.separator)) {
- sLogger.info("Fixing test_data env variable does not end with path separator");
+ sLogger.info("Fixing test_data env variable (does not end with path separator)");
mOsRootDataPath = mOsRootDataPath.concat(File.separator);
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java
index 516e448e6..7e8b0af10 100644
--- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java
@@ -16,17 +16,20 @@
package com.android.ide.eclipse.common.project;
-import junit.framework.TestCase;
+import com.android.ide.eclipse.tests.AdtTestData;
-import com.android.ide.eclipse.mock.FileMock;
+import junit.framework.TestCase;
/**
* Tests for {@link AndroidManifestParser}
*/
public class AndroidManifestParserTest extends TestCase {
- private AndroidManifestParser mManifest;
+ private AndroidManifestParser mManifestTestApp;
+ private AndroidManifestParser mManifestInstrumentation;
- private static final String PACKAGE_NAME = "com.android.testapp"; //$NON-NLS-1$
+ private static final String INSTRUMENTATION_XML = "AndroidManifest-instrumentation.xml"; //$NON-NLS-1$
+ private static final String TESTAPP_XML = "AndroidManifest-testapp.xml"; //$NON-NLS-1$
+ private static final String PACKAGE_NAME = "com.android.testapp"; //$NON-NLS-1$
private static final String ACTIVITY_NAME = "com.android.testapp.MainActivity"; //$NON-NLS-1$
private static final String LIBRARY_NAME = "android.test.runner"; //$NON-NLS-1$
private static final String INSTRUMENTATION_NAME = "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
@@ -35,60 +38,46 @@ public class AndroidManifestParserTest extends TestCase {
protected void setUp() throws Exception {
super.setUp();
- // create the test data
- StringBuilder sb = new StringBuilder();
- sb.append("\n"); //$NON-NLS-1$
- sb.append("\n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \"\n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" \n"); //$NON-NLS-1$
- sb.append(" "); //$NON-NLS-1$
- sb.append(" \n");
- sb.append("\n"); //$NON-NLS-1$
-
- FileMock mockFile = new FileMock("AndroidManifest.xml", sb.toString().getBytes());
+ String testFilePath = AdtTestData.getInstance().getTestFilePath(
+ TESTAPP_XML);
+ mManifestTestApp = AndroidManifestParser.parseForData(testFilePath);
+ assertNotNull(mManifestTestApp);
- mManifest = AndroidManifestParser.parseForData(mockFile);
- assertNotNull(mManifest);
+ testFilePath = AdtTestData.getInstance().getTestFilePath(
+ INSTRUMENTATION_XML);
+ mManifestInstrumentation = AndroidManifestParser.parseForData(testFilePath);
+ assertNotNull(mManifestInstrumentation);
}
+ public void testGetInstrumentationInformation() {
+ assertEquals(1, mManifestInstrumentation.getInstrumentations().length);
+ assertEquals(INSTRUMENTATION_NAME, mManifestTestApp.getInstrumentations()[0]);
+ }
+
public void testGetPackage() {
- assertEquals("com.android.testapp", mManifest.getPackage());
+ assertEquals(PACKAGE_NAME, mManifestTestApp.getPackage());
}
public void testGetActivities() {
- assertEquals(1, mManifest.getActivities().length);
- assertEquals(ACTIVITY_NAME, mManifest.getActivities()[0]);
+ assertEquals(1, mManifestTestApp.getActivities().length);
+ assertEquals(ACTIVITY_NAME, mManifestTestApp.getActivities()[0]);
}
public void testGetLauncherActivity() {
- assertEquals(ACTIVITY_NAME, mManifest.getLauncherActivity());
+ assertEquals(ACTIVITY_NAME, mManifestTestApp.getLauncherActivity());
}
public void testGetUsesLibraries() {
- assertEquals(1, mManifest.getUsesLibraries().length);
- assertEquals(LIBRARY_NAME, mManifest.getUsesLibraries()[0]);
+ assertEquals(1, mManifestTestApp.getUsesLibraries().length);
+ assertEquals(LIBRARY_NAME, mManifestTestApp.getUsesLibraries()[0]);
}
public void testGetInstrumentations() {
- assertEquals(1, mManifest.getInstrumentations().length);
- assertEquals(INSTRUMENTATION_NAME, mManifest.getInstrumentations()[0]);
+ assertEquals(1, mManifestTestApp.getInstrumentations().length);
+ assertEquals(INSTRUMENTATION_NAME, mManifestTestApp.getInstrumentations()[0]);
+ }
+
+ public void testGetPackageName() {
+ assertEquals(PACKAGE_NAME, mManifestTestApp.getPackage());
}
}
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/AndroidManifest-instrumentation.xml b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/AndroidManifest-instrumentation.xml
new file mode 100644
index 000000000..b380f967e
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/AndroidManifest-instrumentation.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/AndroidManifest-testapp.xml b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/AndroidManifest-testapp.xml
new file mode 100644
index 000000000..8ae70121c
--- /dev/null
+++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/data/AndroidManifest-testapp.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ "
+
+
+
+
+ "
+
+
\ No newline at end of file
diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
index 65cbbe356..93577e42b 100644
--- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
+++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
@@ -177,7 +177,7 @@ public final class AvdManager {
public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
mSdk = sdk;
mSdkLog = sdkLog;
- buildAvdList();
+ buildAvdList(mAvdList);
}
/**
@@ -201,6 +201,20 @@ public final class AvdManager {
return null;
}
+
+ /**
+ * Reloads the AVD list.
+ * @throws AndroidLocationException if there was an error finding the location of the
+ * AVD folder.
+ */
+ public void reloadAvds() throws AndroidLocationException {
+ // build the list in a temp list first, in case the method throws an exception.
+ // It's better than deleting the whole list before reading the new one.
+ ArrayList list = new ArrayList();
+ buildAvdList(list);
+ mAvdList.clear();
+ mAvdList.addAll(list);
+ }
/**
* Creates a new AVD. It is expected that there is no existing AVD with this name already.
@@ -620,7 +634,7 @@ public final class AvdManager {
}
}
- private void buildAvdList() throws AndroidLocationException {
+ private void buildAvdList(ArrayList list) throws AndroidLocationException {
// get the Android prefs location.
String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
@@ -664,7 +678,7 @@ public final class AvdManager {
for (File avd : avds) {
AvdInfo info = parseAvdInfo(avd);
if (info != null) {
- mAvdList.add(info);
+ list.add(info);
if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] Added AVD '%s'\n", info.getPath());
}