Merge commit 'korg/cupcake'

This commit is contained in:
The Android Open Source Project
2009-03-27 15:30:35 -07:00
97 changed files with 8553 additions and 1213 deletions

190
apps/CustomLocale/NOTICE Normal file
View File

@@ -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

190
apps/Development/NOTICE Normal file
View File

@@ -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

190
apps/SdkSetup/NOTICE Normal file
View File

@@ -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

190
apps/SpareParts/NOTICE Normal file
View File

@@ -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

View File

@@ -45,6 +45,10 @@
<string name="summary_on_fancy_ime_animations">Use fancier animations for input method windows</string> <string name="summary_on_fancy_ime_animations">Use fancier animations for input method windows</string>
<string name="summary_off_fancy_ime_animations">Use normal animations for input method windows</string> <string name="summary_off_fancy_ime_animations">Use normal animations for input method windows</string>
<string name="title_fancy_rotation_animations">Fancy rotation animations</string>
<string name="summary_on_fancy_rotation_animations">Use fancier animations for screen rotation</string>
<string name="summary_off_fancy_rotation_animations">Use normal animations for screen rotation</string>
<string name="title_haptic_feedback">Haptic feedback</string> <string name="title_haptic_feedback">Haptic feedback</string>
<string name="summary_on_haptic_feedback">Use haptic feedback with user interaction</string> <string name="summary_on_haptic_feedback">Use haptic feedback with user interaction</string>
<string name="summary_off_haptic_feedback">Use haptic feedback with user interaction</string> <string name="summary_off_haptic_feedback">Use haptic feedback with user interaction</string>

View File

@@ -72,6 +72,12 @@
android:summaryOn="@string/summary_on_fancy_ime_animations" android:summaryOn="@string/summary_on_fancy_ime_animations"
android:summaryOff="@string/summary_off_fancy_ime_animations"/> android:summaryOff="@string/summary_off_fancy_ime_animations"/>
<CheckBoxPreference
android:key="fancy_rotation_animations"
android:title="@string/title_fancy_rotation_animations"
android:summaryOn="@string/summary_on_fancy_rotation_animations"
android:summaryOff="@string/summary_off_fancy_rotation_animations"/>
<ListPreference <ListPreference
android:key="font_size" android:key="font_size"
android:title="@string/title_font_size" android:title="@string/title_font_size"

View File

@@ -54,6 +54,7 @@ public class SpareParts extends PreferenceActivity
private static final String WINDOW_ANIMATIONS_PREF = "window_animations"; private static final String WINDOW_ANIMATIONS_PREF = "window_animations";
private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations"; private static final String TRANSITION_ANIMATIONS_PREF = "transition_animations";
private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations"; private static final String FANCY_IME_ANIMATIONS_PREF = "fancy_ime_animations";
private static final String FANCY_ROTATION_ANIMATIONS_PREF = "fancy_rotation_animations";
private static final String HAPTIC_FEEDBACK_PREF = "haptic_feedback"; private static final String HAPTIC_FEEDBACK_PREF = "haptic_feedback";
private static final String FONT_SIZE_PREF = "font_size"; private static final String FONT_SIZE_PREF = "font_size";
private static final String END_BUTTON_PREF = "end_button"; private static final String END_BUTTON_PREF = "end_button";
@@ -64,6 +65,7 @@ public class SpareParts extends PreferenceActivity
private ListPreference mWindowAnimationsPref; private ListPreference mWindowAnimationsPref;
private ListPreference mTransitionAnimationsPref; private ListPreference mTransitionAnimationsPref;
private CheckBoxPreference mFancyImeAnimationsPref; private CheckBoxPreference mFancyImeAnimationsPref;
private CheckBoxPreference mFancyRotationAnimationsPref;
private CheckBoxPreference mHapticFeedbackPref; private CheckBoxPreference mHapticFeedbackPref;
private ListPreference mFontSizePref; private ListPreference mFontSizePref;
private ListPreference mEndButtonPref; private ListPreference mEndButtonPref;
@@ -118,6 +120,7 @@ public class SpareParts extends PreferenceActivity
mTransitionAnimationsPref = (ListPreference) prefSet.findPreference(TRANSITION_ANIMATIONS_PREF); mTransitionAnimationsPref = (ListPreference) prefSet.findPreference(TRANSITION_ANIMATIONS_PREF);
mTransitionAnimationsPref.setOnPreferenceChangeListener(this); mTransitionAnimationsPref.setOnPreferenceChangeListener(this);
mFancyImeAnimationsPref = (CheckBoxPreference) prefSet.findPreference(FANCY_IME_ANIMATIONS_PREF); mFancyImeAnimationsPref = (CheckBoxPreference) prefSet.findPreference(FANCY_IME_ANIMATIONS_PREF);
mFancyRotationAnimationsPref = (CheckBoxPreference) prefSet.findPreference(FANCY_ROTATION_ANIMATIONS_PREF);
mHapticFeedbackPref = (CheckBoxPreference) prefSet.findPreference(HAPTIC_FEEDBACK_PREF); mHapticFeedbackPref = (CheckBoxPreference) prefSet.findPreference(HAPTIC_FEEDBACK_PREF);
mFontSizePref = (ListPreference) prefSet.findPreference(FONT_SIZE_PREF); mFontSizePref = (ListPreference) prefSet.findPreference(FONT_SIZE_PREF);
mFontSizePref.setOnPreferenceChangeListener(this); mFontSizePref.setOnPreferenceChangeListener(this);
@@ -143,6 +146,9 @@ public class SpareParts extends PreferenceActivity
mFancyImeAnimationsPref.setChecked(Settings.System.getInt( mFancyImeAnimationsPref.setChecked(Settings.System.getInt(
getContentResolver(), getContentResolver(),
Settings.System.FANCY_IME_ANIMATIONS, 0) != 0); Settings.System.FANCY_IME_ANIMATIONS, 0) != 0);
mFancyRotationAnimationsPref.setChecked(Settings.System.getInt(
getContentResolver(),
"fancy_rotation_anim", 0) != 0);
mHapticFeedbackPref.setChecked(Settings.System.getInt( mHapticFeedbackPref.setChecked(Settings.System.getInt(
getContentResolver(), getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0); Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0);
@@ -241,6 +247,10 @@ public class SpareParts extends PreferenceActivity
Settings.System.putInt(getContentResolver(), Settings.System.putInt(getContentResolver(),
Settings.System.FANCY_IME_ANIMATIONS, Settings.System.FANCY_IME_ANIMATIONS,
mFancyImeAnimationsPref.isChecked() ? 1 : 0); mFancyImeAnimationsPref.isChecked() ? 1 : 0);
} else if (FANCY_ROTATION_ANIMATIONS_PREF.equals(key)) {
Settings.System.putInt(getContentResolver(),
"fancy_rotation_anim",
mFancyRotationAnimationsPref.isChecked() ? 1 : 0);
} else if (HAPTIC_FEEDBACK_PREF.equals(key)) { } else if (HAPTIC_FEEDBACK_PREF.equals(key)) {
Settings.System.putInt(getContentResolver(), Settings.System.putInt(getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, Settings.System.HAPTIC_FEEDBACK_ENABLED,

190
apps/launchperf/NOTICE Normal file
View File

@@ -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

View File

@@ -369,7 +369,7 @@ public class Monkey {
if (mVerbose >= 2) { // check seeding performance if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed); System.out.println("// Seeded: " + mSeed);
} }
mEventSource = new MonkeySourceRandom(mSeed, mMainApps); mEventSource = new MonkeySourceRandom(mSeed, mMainApps, mThrottle);
mEventSource.setVerbose(mVerbose); mEventSource.setVerbose(mVerbose);
//set any of the factors that has been set //set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) { for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
@@ -709,13 +709,6 @@ public class Monkey {
} }
} }
try {
Thread.sleep(mThrottle);
} catch (InterruptedException e1) {
System.out.println("** Monkey interrupted in sleep.");
return i;
}
// In this debugging mode, we never send any events. This is primarily // In this debugging mode, we never send any events. This is primarily
// here so you can manually test the package or category limits, while manually // here so you can manually test the package or category limits, while manually
// exercising the system. // exercising the system.
@@ -730,7 +723,10 @@ public class Monkey {
MonkeyEvent ev = mEventSource.getNextEvent(); MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) { if (ev != null) {
// We don't want to count throttling as an event.
if (!(ev instanceof MonkeyThrottleEvent)) {
i++; i++;
}
int injectCode = ev.injectEvent(mWm, mAm, mVerbose); int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
if (injectCode == MonkeyEvent.INJECT_FAIL) { if (injectCode == MonkeyEvent.INJECT_FAIL) {
if (ev instanceof MonkeyKeyEvent) { if (ev instanceof MonkeyKeyEvent) {

View File

@@ -29,6 +29,7 @@ public abstract class MonkeyEvent {
public static final int EVENT_TYPE_TRACKBALL = 2; public static final int EVENT_TYPE_TRACKBALL = 2;
public static final int EVENT_TYPE_ACTIVITY = 3; public static final int EVENT_TYPE_ACTIVITY = 3;
public static final int EVENT_TYPE_FLIP = 4; // Keyboard flip public static final int EVENT_TYPE_FLIP = 4; // Keyboard flip
public static final int EVENT_TYPE_THROTTLE = 5;
public static final int INJECT_SUCCESS = 1; public static final int INJECT_SUCCESS = 1;
public static final int INJECT_FAIL = 0; public static final int INJECT_FAIL = 0;

View File

@@ -171,6 +171,7 @@ public class MonkeySourceRandom implements MonkeyEventSource{
private LinkedList<MonkeyEvent> mQ = new LinkedList<MonkeyEvent>(); private LinkedList<MonkeyEvent> mQ = new LinkedList<MonkeyEvent>();
private Random mRandom; private Random mRandom;
private int mVerbose = 0; private int mVerbose = 0;
private long mThrottle = 0;
private boolean mKeyboardOpen = false; private boolean mKeyboardOpen = false;
@@ -185,7 +186,7 @@ public class MonkeySourceRandom implements MonkeyEventSource{
return KEY_NAMES[keycode]; return KEY_NAMES[keycode];
} }
public MonkeySourceRandom(long seed, ArrayList<ComponentName> MainApps) { public MonkeySourceRandom(long seed, ArrayList<ComponentName> MainApps, long throttle) {
// default values for random distributions // default values for random distributions
// note, these are straight percentages, to match user input (cmd line args) // note, these are straight percentages, to match user input (cmd line args)
// but they will be converted to 0..1 values before the main loop runs. // but they will be converted to 0..1 values before the main loop runs.
@@ -202,6 +203,7 @@ public class MonkeySourceRandom implements MonkeyEventSource{
mRandom = new SecureRandom(); mRandom = new SecureRandom();
mRandom.setSeed((seed == 0) ? -1 : seed); mRandom.setSeed((seed == 0) ? -1 : seed);
mMainApps = MainApps; mMainApps = MainApps;
mThrottle = throttle;
} }
/** /**
@@ -334,6 +336,7 @@ public class MonkeySourceRandom implements MonkeyEventSource{
downAt, MotionEvent.ACTION_UP, x, y, 0); downAt, MotionEvent.ACTION_UP, x, y, 0);
e.setIntermediateNote(false); e.setIntermediateNote(false);
mQ.addLast(e); mQ.addLast(e);
addThrottle();
} }
/** /**
@@ -384,6 +387,7 @@ public class MonkeySourceRandom implements MonkeyEventSource{
e.setIntermediateNote(false); e.setIntermediateNote(false);
mQ.addLast(e); mQ.addLast(e);
} }
addThrottle();
} }
/** /**
@@ -416,11 +420,13 @@ public class MonkeySourceRandom implements MonkeyEventSource{
MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get( MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
mRandom.nextInt(mMainApps.size()))); mRandom.nextInt(mMainApps.size())));
mQ.addLast(e); mQ.addLast(e);
addThrottle();
return; return;
} else if (cls < mFactors[FACTOR_FLIP]) { } else if (cls < mFactors[FACTOR_FLIP]) {
MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen); MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
mKeyboardOpen = !mKeyboardOpen; mKeyboardOpen = !mKeyboardOpen;
mQ.addLast(e); mQ.addLast(e);
addThrottle();
return; return;
} else { } else {
lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1); lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
@@ -431,6 +437,8 @@ public class MonkeySourceRandom implements MonkeyEventSource{
e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey); e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
mQ.addLast(e); mQ.addLast(e);
addThrottle();
} }
public boolean validate() { public boolean validate() {
@@ -464,4 +472,8 @@ public class MonkeySourceRandom implements MonkeyEventSource{
mQ.removeFirst(); mQ.removeFirst();
return e; return e;
} }
private void addThrottle() {
mQ.addLast(new MonkeyThrottleEvent(MonkeyEvent.EVENT_TYPE_THROTTLE, mThrottle));
}
} }

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.commands.monkey;
import android.app.IActivityManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.view.IWindowManager;
import android.view.MotionEvent;
/**
* monkey throttle event
*/
public class MonkeyThrottleEvent extends MonkeyEvent {
private long mThrottle;
public MonkeyThrottleEvent(int type, long throttle) {
super(type);
mThrottle = throttle;
}
@Override
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
if (verbose > 1) {
System.out.println("Sleeping for " + mThrottle + " milliseconds");
}
try {
Thread.sleep(mThrottle);
} catch (InterruptedException e1) {
System.out.println("** Monkey interrupted in sleep.");
return MonkeyEvent.INJECT_FAIL;
}
return MonkeyEvent.INJECT_SUCCESS;
}
}

View File

@@ -11,5 +11,6 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \ libcutils \
LOCAL_MODULE:= qemud LOCAL_MODULE:= qemud
LOCAL_MODULE_TAGS := debug
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH := $(call my-dir)
ifneq ($(TARGET_PRODUCT),sim)
# HAL module implemenation, not prelinked and stored in
# hw/<SENSORS_HARDWARE_MODULE_ID>.<ro.hardware>.so
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog libcutils
LOCAL_SRC_FILES := sensors_qemu.c
LOCAL_MODULE := sensors.goldfish
LOCAL_MODULE_TAGS := debug
include $(BUILD_SHARED_LIBRARY)
endif

View File

@@ -0,0 +1,591 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* this implements a sensors hardware library for the Android emulator.
* the following code should be built as a shared library that will be
* placed into /system/lib/hw/sensors.goldfish.so
*
* it will be loaded by the code in hardware/libhardware/hardware.c
* which is itself called from com_android_server_SensorService.cpp
*/
/* we connect with the emulator through the "sensors" qemud service
*/
#define SENSORS_SERVICE_NAME "sensors"
#define LOG_TAG "QemuSensors"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <cutils/log.h>
#include <cutils/sockets.h>
#include <hardware/sensors.h>
#if 0
#define D(...) LOGD(__VA_ARGS__)
#else
#define D(...) ((void)0)
#endif
#define E(...) LOGE(__VA_ARGS__)
#include <hardware/qemud.h>
/** SENSOR IDS AND NAMES
**/
#define MAX_NUM_SENSORS 4
#define SUPPORTED_SENSORS ((1<<MAX_NUM_SENSORS)-1)
#define ID_BASE SENSORS_HANDLE_BASE
#define ID_ACCELERATION (ID_BASE+0)
#define ID_MAGNETIC_FIELD (ID_BASE+1)
#define ID_ORIENTATION (ID_BASE+2)
#define ID_TEMPERATURE (ID_BASE+3)
#define SENSORS_ACCELERATION (1 << ID_ACCELERATION)
#define SENSORS_MAGNETIC_FIELD (1 << ID_MAGNETIC_FIELD)
#define SENSORS_ORIENTATION (1 << ID_ORIENTATION)
#define SENSORS_TEMPERATURE (1 << ID_TEMPERATURE)
#define ID_CHECK(x) ((unsigned)((x)-ID_BASE) < 4)
#define SENSORS_LIST \
SENSOR_(ACCELERATION,"acceleration") \
SENSOR_(MAGNETIC_FIELD,"magnetic-field") \
SENSOR_(ORIENTATION,"orientation") \
SENSOR_(TEMPERATURE,"temperature") \
static const struct {
const char* name;
int id; } _sensorIds[MAX_NUM_SENSORS] =
{
#define SENSOR_(x,y) { y, ID_##x },
SENSORS_LIST
#undef SENSOR_
};
static const char*
_sensorIdToName( int id )
{
int nn;
for (nn = 0; nn < MAX_NUM_SENSORS; nn++)
if (id == _sensorIds[nn].id)
return _sensorIds[nn].name;
return "<UNKNOWN>";
}
static int
_sensorIdFromName( const char* name )
{
int nn;
if (name == NULL)
return -1;
for (nn = 0; nn < MAX_NUM_SENSORS; nn++)
if (!strcmp(name, _sensorIds[nn].name))
return _sensorIds[nn].id;
return -1;
}
/** SENSORS CONTROL DEVICE
**
** This one is used to send commands to the sensors drivers.
** We implement this by sending directly commands to the emulator
** through the QEMUD channel.
**/
typedef struct SensorControl {
struct sensors_control_device_t device;
int fd;
uint32_t active_sensors;
} SensorControl;
/* this must return a file descriptor that will be used to read
* the sensors data (it is passed to data__data_open() below
*/
static int
control__open_data_source(struct sensors_control_device_t *dev)
{
SensorControl* ctl = (void*)dev;
if (ctl->fd < 0) {
ctl->fd = qemud_channel_open(SENSORS_SERVICE_NAME);
}
D("%s: fd=%d", __FUNCTION__, ctl->fd);
return ctl->fd;
}
static int
control__activate(struct sensors_control_device_t *dev,
int handle,
int enabled)
{
SensorControl* ctl = (void*)dev;
uint32_t mask, sensors, active, new_sensors, changed;
char command[128];
int ret;
D("%s: handle=%s (%d) enabled=%d", __FUNCTION__,
_sensorIdToName(handle), handle, enabled);
if (!ID_CHECK(handle)) {
E("%s: bad handle ID", __FUNCTION__);
return -1;
}
mask = (1<<handle);
sensors = enabled ? mask : 0;
active = ctl->active_sensors;
new_sensors = (active & ~mask) | (sensors & mask);
changed = active ^ new_sensors;
if (!changed)
return 0;
snprintf(command, sizeof command, "set:%s:%d",
_sensorIdToName(handle), enabled != 0);
if (ctl->fd < 0) {
ctl->fd = qemud_channel_open(SENSORS_SERVICE_NAME);
}
ret = qemud_channel_send(ctl->fd, command, -1);
if (ret < 0)
return -1;
ctl->active_sensors = new_sensors;
return 0;
}
static int
control__set_delay(struct sensors_control_device_t *dev, int32_t ms)
{
SensorControl* ctl = (void*)dev;
char command[128];
D("%s: dev=%p delay-ms=%d", __FUNCTION__, dev, ms);
snprintf(command, sizeof command, "set-delay:%d", ms);
return qemud_channel_send(ctl->fd, command, -1);
}
/* this function is used to force-stop the blocking read() in
* data__poll. In order to keep the implementation as simple
* as possible here, we send a command to the emulator which
* shall send back an appropriate data block to the system.
*/
static int
control__wake(struct sensors_control_device_t *dev)
{
SensorControl* ctl = (void*)dev;
D("%s: dev=%p", __FUNCTION__, dev);
return qemud_channel_send(ctl->fd, "wake", -1);
}
static int
control__close(struct hw_device_t *dev)
{
SensorControl* ctl = (void*)dev;
close(ctl->fd);
free(ctl);
return 0;
}
/** SENSORS DATA DEVICE
**
** This one is used to read sensor data from the hardware.
** We implement this by simply reading the data from the
** emulator through the QEMUD channel.
**/
typedef struct SensorData {
struct sensors_data_device_t device;
sensors_data_t sensors[MAX_NUM_SENSORS];
int events_fd;
uint32_t pendingSensors;
int64_t timeStart;
int64_t timeOffset;
} SensorData;
/* return the current time in nanoseconds */
static int64_t
data__now_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
}
static int
data__data_open(struct sensors_data_device_t *dev, int fd)
{
SensorData* data = (void*)dev;
int i;
D("%s: dev=%p fd=%d", __FUNCTION__, dev, fd);
memset(&data->sensors, 0, sizeof(data->sensors));
for (i=0 ; i<MAX_NUM_SENSORS ; i++) {
data->sensors[i].vector.status = SENSOR_STATUS_ACCURACY_HIGH;
}
data->pendingSensors = 0;
data->timeStart = 0;
data->timeOffset = 0;
data->events_fd = dup(fd);
return 0;
}
static int
data__data_close(struct sensors_data_device_t *dev)
{
SensorData* data = (void*)dev;
D("%s: dev=%p", __FUNCTION__, dev);
if (data->events_fd > 0) {
close(data->events_fd);
data->events_fd = -1;
}
return 0;
}
static int
pick_sensor(SensorData* data,
sensors_data_t* values)
{
uint32_t mask = SUPPORTED_SENSORS;
while (mask) {
uint32_t i = 31 - __builtin_clz(mask);
mask &= ~(1<<i);
if (data->pendingSensors & (1<<i)) {
data->pendingSensors &= ~(1<<i);
*values = data->sensors[i];
values->sensor = (1<<i);
LOGD_IF(0, "%s: %d [%f, %f, %f]", __FUNCTION__,
(1<<i),
values->vector.x,
values->vector.y,
values->vector.z);
return i;
}
}
LOGE("No sensor to return!!! pendingSensors=%08x", data->pendingSensors);
// we may end-up in a busy loop, slow things down, just in case.
usleep(100000);
return -1;
}
static int
data__poll(struct sensors_data_device_t *dev, sensors_data_t* values)
{
SensorData* data = (void*)dev;
int fd = data->events_fd;
D("%s: data=%p", __FUNCTION__, dev);
// there are pending sensors, returns them now...
if (data->pendingSensors) {
return pick_sensor(data, values);
}
// wait until we get a complete event for an enabled sensor
uint32_t new_sensors = 0;
while (1) {
/* read the next event */
char buff[256];
int len = qemud_channel_recv(data->events_fd, buff, sizeof buff-1);
float params[3];
int64_t event_time;
if (len < 0)
continue;
buff[len] = 0;
/* "wake" is sent from the emulator to exit this loop. This shall
* really be because another thread called "control__wake" in this
* process.
*/
if (!strcmp((const char*)data, "wake")) {
return 0x7FFFFFFF;
}
/* "acceleration:<x>:<y>:<z>" corresponds to an acceleration event */
if (sscanf(buff, "acceleration:%g:%g:%g", params+0, params+1, params+2) == 3) {
new_sensors |= SENSORS_ACCELERATION;
data->sensors[ID_ACCELERATION].acceleration.x = params[0];
data->sensors[ID_ACCELERATION].acceleration.y = params[1];
data->sensors[ID_ACCELERATION].acceleration.z = params[2];
continue;
}
/* "orientation:<azimuth>:<pitch>:<roll>" is sent when orientation changes */
if (sscanf(buff, "orientation:%g:%g:%g", params+0, params+1, params+2) == 3) {
new_sensors |= SENSORS_ORIENTATION;
data->sensors[ID_ORIENTATION].orientation.azimuth = params[0];
data->sensors[ID_ORIENTATION].orientation.pitch = params[1];
data->sensors[ID_ORIENTATION].orientation.roll = params[2];
continue;
}
/* "magnetic:<x>:<y>:<z>" is sent for the params of the magnetic field */
if (sscanf(buff, "magnetic:%g:%g:%g", params+0, params+1, params+2) == 3) {
new_sensors |= SENSORS_MAGNETIC_FIELD;
data->sensors[ID_MAGNETIC_FIELD].magnetic.x = params[0];
data->sensors[ID_MAGNETIC_FIELD].magnetic.y = params[1];
data->sensors[ID_MAGNETIC_FIELD].magnetic.z = params[2];
continue;
}
/* "temperature:<celsius>" */
if (sscanf(buff, "temperature:%g", params+0) == 2) {
new_sensors |= SENSORS_TEMPERATURE;
data->sensors[ID_TEMPERATURE].temperature = params[0];
continue;
}
/* "sync:<time>" is sent after a series of sensor events.
* where 'time' is expressed in micro-seconds and corresponds
* to the VM time when the real poll occured.
*/
if (sscanf(buff, "sync:%lld", &event_time) == 1) {
if (new_sensors) {
data->pendingSensors = new_sensors;
int64_t t = event_time * 1000LL; /* convert to nano-seconds */
/* use the time at the first sync: as the base for later
* time values */
if (data->timeStart == 0) {
data->timeStart = data__now_ns();
data->timeOffset = data->timeStart - t;
}
t += data->timeOffset;
while (new_sensors) {
uint32_t i = 31 - __builtin_clz(new_sensors);
new_sensors &= ~(1<<i);
data->sensors[i].time = t;
}
return pick_sensor(data, values);
} else {
D("huh ? sync without any sensor data ?");
}
continue;
}
D("huh ? unsupported command");
}
}
static int
data__close(struct hw_device_t *dev)
{
SensorData* data = (SensorData*)dev;
if (data) {
if (data->events_fd > 0) {
//LOGD("(device close) about to close fd=%d", data->events_fd);
close(data->events_fd);
}
free(data);
}
return 0;
}
/** MODULE REGISTRATION SUPPORT
**
** This is required so that hardware/libhardware/hardware.c
** will dlopen() this library appropriately.
**/
/*
* the following is the list of all supported sensors.
* this table is used to build sSensorList declared below
* according to which hardware sensors are reported as
* available from the emulator (see get_sensors_list below)
*
* note: numerical values for maxRange/resolution/power were
* taken from the reference AK8976A implementation
*/
static const struct sensor_t sSensorListInit[] = {
{ .name = "Goldfish 3-axis Accelerometer",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_ACCELERATION,
.type = SENSOR_TYPE_ACCELEROMETER,
.maxRange = 2.8f,
.resolution = 1.0f/4032.0f,
.power = 3.0f,
.reserved = {}
},
{ .name = "Goldfish 3-axis Magnetic field sensor",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_MAGNETIC_FIELD,
.type = SENSOR_TYPE_MAGNETIC_FIELD,
.maxRange = 2000.0f,
.resolution = 1.0f,
.power = 6.7f,
.reserved = {}
},
{ .name = "Goldfish Orientation sensor",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_ORIENTATION,
.type = SENSOR_TYPE_ORIENTATION,
.maxRange = 360.0f,
.resolution = 1.0f,
.power = 9.7f,
.reserved = {}
},
{ .name = "Goldfish Temperature sensor",
.vendor = "The Android Open Source Project",
.version = 1,
.handle = ID_TEMPERATURE,
.type = SENSOR_TYPE_TEMPERATURE,
.maxRange = 80.0f,
.resolution = 1.0f,
.power = 0.0f,
.reserved = {}
},
};
static struct sensor_t sSensorList[MAX_NUM_SENSORS];
static uint32_t sensors__get_sensors_list(struct sensors_module_t* module,
struct sensor_t const** list)
{
int fd = qemud_channel_open(SENSORS_SERVICE_NAME);
char buffer[12];
int mask, nn, count;
int ret;
if (fd < 0) {
E("%s: no qemud connection", __FUNCTION__);
return 0;
}
ret = qemud_channel_send(fd, "list-sensors", -1);
if (ret < 0) {
E("%s: could not query sensor list: %s", __FUNCTION__,
strerror(errno));
close(fd);
return 0;
}
ret = qemud_channel_recv(fd, buffer, sizeof buffer-1);
if (ret < 0) {
E("%s: could not receive sensor list: %s", __FUNCTION__,
strerror(errno));
close(fd);
return 0;
}
buffer[ret] = 0;
close(fd);
/* the result is a integer used as a mask for available sensors */
mask = atoi(buffer);
count = 0;
for (nn = 0; nn < MAX_NUM_SENSORS; nn++) {
if (((1 << nn) & mask) == 0)
continue;
sSensorList[count++] = sSensorListInit[nn];
}
D("%s: returned %d sensors (mask=%d)", __FUNCTION__, count, mask);
*list = sSensorList;
return count;
}
static int
open_sensors(const struct hw_module_t* module,
const char* name,
struct hw_device_t* *device)
{
int status = -EINVAL;
D("%s: name=%s", __FUNCTION__, name);
if (!strcmp(name, SENSORS_HARDWARE_CONTROL))
{
SensorControl *dev = malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = (struct hw_module_t*) module;
dev->device.common.close = control__close;
dev->device.open_data_source = control__open_data_source;
dev->device.activate = control__activate;
dev->device.set_delay = control__set_delay;
dev->device.wake = control__wake;
dev->fd = -1;
*device = &dev->device.common;
status = 0;
}
else if (!strcmp(name, SENSORS_HARDWARE_DATA)) {
SensorData *dev = malloc(sizeof(*dev));
memset(dev, 0, sizeof(*dev));
dev->device.common.tag = HARDWARE_DEVICE_TAG;
dev->device.common.version = 0;
dev->device.common.module = (struct hw_module_t*) module;
dev->device.common.close = data__close;
dev->device.data_open = data__data_open;
dev->device.data_close = data__data_close;
dev->device.poll = data__poll;
dev->events_fd = -1;
*device = &dev->device.common;
status = 0;
}
return status;
}
static struct hw_module_methods_t sensors_module_methods = {
.open = open_sensors
};
const struct sensors_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = SENSORS_HARDWARE_MODULE_ID,
.name = "Goldfish SENSORS Module",
.author = "The Android Open Source Project",
.methods = &sensors_module_methods,
},
.get_sensors_list = sensors__get_sensors_list
};

View File

@@ -1,6 +1,9 @@
Building the pdk (platform development kit) Building the pdk (platform development kit)
1) get a cupcake source tree 1) get a cupcake source tree with all the normal tools... and add doxygen
(We currently support version 1.4.6)
sudo apt-get install doxygen
2) from the root 2) from the root
. build/envsetup.sh . build/envsetup.sh

View File

@@ -33,8 +33,6 @@
android:label="@string/activity_sample_code" android:label="@string/activity_sample_code"
android:icon="@drawable/app_sample_code" > android:icon="@drawable/app_sample_code" >
<uses-library android:name="com.google.android.maps" />
<activity android:name="ApiDemos"> <activity android:name="ApiDemos">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -1305,20 +1303,6 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".view.MapViewDemo" android:label="Views/MapView">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity android:name=".view.MapViewCompassDemo" android:label="Views/MapView and Compass">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.SAMPLE_CODE" />
</intent-filter>
</activity>
<!-- ************************************* --> <!-- ************************************* -->
<!-- GRAPHICS SAMPLES --> <!-- GRAPHICS SAMPLES -->
<!-- ************************************* --> <!-- ************************************* -->

View File

@@ -16,17 +16,18 @@
package com.example.android.apis.app; package com.example.android.apis.app;
import com.example.android.apis.R;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.ListView; import android.widget.ListView;
import com.example.android.apis.R;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
@@ -71,9 +72,10 @@ public class VoiceRecognition extends Activity implements OnClickListener {
*/ */
private void startVoiceRecognitionActivity() { private void startVoiceRecognitionActivity() {
//TODO Get these values from constants //TODO Get these values from constants
Intent intent = new Intent("android.speech.action.RECOGNIZE_SPEECH"); Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra("language_model", "free_form"); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
intent.putExtra("prompt", "Speech recognition demo"); RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speech recognition demo");
startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE); startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
} }
@@ -83,8 +85,8 @@ public class VoiceRecognition extends Activity implements OnClickListener {
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == VOICE_RECOGNITION_REQUEST_CODE && resultCode == RESULT_OK) { if (requestCode == VOICE_RECOGNITION_REQUEST_CODE && resultCode == RESULT_OK) {
//TODO get the value from a constant ArrayList<String> matches = data.getStringArrayListExtra(
ArrayList<String>matches = data.getStringArrayListExtra("results"); RecognizerIntent.EXTRA_RESULTS);
mList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mList.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
matches)); matches));
} }

View File

@@ -34,6 +34,10 @@ public class TranslucentGLSurfaceViewActivity extends Activity {
// Create our Preview view and set it as the content of our // Create our Preview view and set it as the content of our
// Activity // Activity
mGLSurfaceView = new GLSurfaceView(this); mGLSurfaceView = new GLSurfaceView(this);
// We want an 8888 pixel format because that's required for
// a translucent window.
// And we want a depth buffer.
mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
// Tell the cube renderer that we want to render a translucent version // Tell the cube renderer that we want to render a translucent version
// of the cube: // of the cube:
mGLSurfaceView.setRenderer(new CubeRenderer(true)); mGLSurfaceView.setRenderer(new CubeRenderer(true));

View File

@@ -26,6 +26,7 @@ public class TriangleActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(this); mGLView = new GLSurfaceView(this);
mGLView.setEGLConfigChooser(false);
mGLView.setRenderer(new TriangleRenderer(this)); mGLView.setRenderer(new TriangleRenderer(this));
setContentView(mGLView); setContentView(mGLView);
} }

View File

@@ -44,16 +44,6 @@ public class TriangleRenderer implements GLSurfaceView.Renderer{
mTriangle = new Triangle(); mTriangle = new Triangle();
} }
public int[] getConfigSpec() {
// We don't need a depth buffer, and don't care about our
// color depth.
int[] configSpec = {
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_NONE
};
return configSpec;
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) { public void onSurfaceCreated(GL10 gl, EGLConfig config) {
/* /*
* By default, OpenGL enables features that improve quality * By default, OpenGL enables features that improve quality

View File

@@ -74,15 +74,6 @@ class KubeRenderer implements GLSurfaceView.Renderer {
mWorld.draw(gl); mWorld.draw(gl);
} }
public int[] getConfigSpec() {
// Need a depth buffer, don't care about color depth.
int[] configSpec = {
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_NONE
};
return configSpec;
}
public void onSurfaceChanged(GL10 gl, int width, int height) { public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height); gl.glViewport(0, 0, width, height);

View File

@@ -89,16 +89,6 @@ public class CompassActivity extends Activity implements Renderer, SensorEventLi
mSensorManager.unregisterListener(this); mSensorManager.unregisterListener(this);
} }
public int[] getConfigSpec() {
// We want a depth buffer, don't care about the
// details of the color buffer.
int[] configSpec = {
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_NONE
};
return configSpec;
}
public void onDrawFrame(GL10 gl) { public void onDrawFrame(GL10 gl) {
/* /*
* Usually, the first thing one might want to do is to clear * Usually, the first thing one might want to do is to clear

View File

@@ -550,7 +550,7 @@ public class SoftKeyboard extends InputMethodService
boolean typedWordValid) { boolean typedWordValid) {
if (suggestions != null && suggestions.size() > 0) { if (suggestions != null && suggestions.size() > 0) {
setCandidatesViewShown(true); setCandidatesViewShown(true);
} else if (isFullscreenMode()) { } else if (isExtractViewShown()) {
setCandidatesViewShown(true); setCandidatesViewShown(true);
} }
if (mCandidateView != null) { if (mCandidateView != null) {

19
testrunner/Android.mk Normal file
View File

@@ -0,0 +1,19 @@
#
# Install a list of test definitions on device
#
# where to install the sample files on the device
#
local_target_dir := $(TARGET_OUT_DATA)/testinfo
LOCAL_PATH := $(call my-dir)
########################
include $(CLEAR_VARS)
LOCAL_MODULE := tests.xml
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(local_target_dir)
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)

View File

@@ -0,0 +1,45 @@
#!/usr/bin/python2.4
#
#
# Copyright 2008, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Contains utility functions for interacting with the Android build system."""
# Python imports
import os
# local imports
import errors
import logger
def GetTop():
"""Returns the full pathname of the "top" of the Android development tree.
Assumes build environment has been properly configured by envsetup &
lunch/choosecombo.
Returns:
the absolute file path of the Android build root.
Raises:
AbortError: if Android build root could not be found.
"""
# TODO: does this need to be reimplemented to be like gettop() in envsetup.sh
root_path = os.getenv('ANDROID_BUILD_TOP')
if root_path is None:
logger.Log('Error: ANDROID_BUILD_TOP not defined. Please run envsetup.sh')
raise errors.AbortError
return root_path

View File

@@ -108,4 +108,8 @@
<coverage_target name="TelephonyProvider" <coverage_target name="TelephonyProvider"
build_path="packages/providers/telephony" type="APPS" /> build_path="packages/providers/telephony" type="APPS" />
<!-- input methods -->
<coverage_target name="LatinIME" build_path="packages/inputmethods/LatinIME"
type="APPS" />
</coverage_targets> </coverage_targets>

280
testrunner/runtest.py Executable file
View File

@@ -0,0 +1,280 @@
#!/usr/bin/python2.4
#
# Copyright 2008, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command line utility for running a pre-defined test.
Based on previous <androidroot>/development/tools/runtest shell script.
"""
# Python imports
import glob
import optparse
import os
from sets import Set
import sys
# local imports
import adb_interface
import android_build
import coverage
import errors
import logger
import run_command
import test_defs
class TestRunner(object):
"""Command line utility class for running pre-defined Android test(s)."""
# file path to android core platform tests, relative to android build root
# TODO move these test data files to another directory
_CORE_TEST_PATH = os.path.join("development", "testrunner", "tests.xml")
# vendor glob file path patterns to tests, relative to android
# build root
_VENDOR_TEST_PATH = os.path.join("vendor", "*", "tests", "testinfo",
"tests.xml")
_RUNTEST_USAGE = (
"usage: runtest.py [options] short-test-name[s]\n\n"
"The runtest script works in two ways. You can query it "
"for a list of tests, or you can launch one or more tests.")
def _ProcessOptions(self):
"""Processes command-line options."""
# TODO error messages on once-only or mutually-exclusive options.
user_test_default = os.path.join(os.environ.get("HOME"), ".android",
"tests.xml")
parser = optparse.OptionParser(usage=self._RUNTEST_USAGE)
parser.add_option("-l", "--list-tests", dest="only_list_tests",
default=False, action="store_true",
help="To view the list of tests")
parser.add_option("-b", "--skip-build", dest="skip_build", default=False,
action="store_true", help="Skip build - just launch")
parser.add_option("-n", "--skip_execute", dest="preview", default=False,
action="store_true",
help="Do not execute, just preview commands")
parser.add_option("-r", "--raw-mode", dest="raw_mode", default=False,
action="store_true",
help="Raw mode (for output to other tools)")
parser.add_option("-a", "--suite-assign", dest="suite_assign_mode",
default=False, action="store_true",
help="Suite assignment (for details & usage see "
"InstrumentationTestRunner)")
parser.add_option("-v", "--verbose", dest="verbose", default=False,
action="store_true",
help="Increase verbosity of %s" % sys.argv[0])
parser.add_option("-w", "--wait-for-debugger", dest="wait_for_debugger",
default=False, action="store_true",
help="Wait for debugger before launching tests")
parser.add_option("-c", "--test-class", dest="test_class",
help="Restrict test to a specific class")
parser.add_option("-m", "--test-method", dest="test_method",
help="Restrict test to a specific method")
parser.add_option("-u", "--user-tests-file", dest="user_tests_file",
metavar="FILE", default=user_test_default,
help="Alternate source of user test definitions")
parser.add_option("-o", "--coverage", dest="coverage",
default=False, action="store_true",
help="Generate code coverage metrics for test(s)")
parser.add_option("-t", "--all-tests", dest="all_tests",
default=False, action="store_true",
help="Run all defined tests")
parser.add_option("--continuous", dest="continuous_tests",
default=False, action="store_true",
help="Run all tests defined as part of the continuous "
"test set")
group = optparse.OptionGroup(
parser, "Targets", "Use these options to direct tests to a specific "
"Android target")
group.add_option("-e", "--emulator", dest="emulator", default=False,
action="store_true", help="use emulator")
group.add_option("-d", "--device", dest="device", default=False,
action="store_true", help="use device")
group.add_option("-s", "--serial", dest="serial",
help="use specific serial")
parser.add_option_group(group)
self._options, self._test_args = parser.parse_args()
if (not self._options.only_list_tests and not self._options.all_tests
and not self._options.continuous_tests and len(self._test_args) < 1):
parser.print_help()
logger.SilentLog("at least one test name must be specified")
raise errors.AbortError
self._adb = adb_interface.AdbInterface()
if self._options.emulator:
self._adb.SetEmulatorTarget()
elif self._options.device:
self._adb.SetDeviceTarget()
elif self._options.serial is not None:
self._adb.SetTargetSerial(self._options.serial)
if self._options.verbose:
logger.SetVerbose(True)
self._root_path = android_build.GetTop()
self._known_tests = self._ReadTests()
self._coverage_gen = coverage.CoverageGenerator(
android_root_path=self._root_path, adb_interface=self._adb)
def _ReadTests(self):
"""Parses the set of test definition data.
Returns:
A TestDefinitions object that contains the set of parsed tests.
Raises:
AbortError: If a fatal error occurred when parsing the tests.
"""
core_test_path = os.path.join(self._root_path, self._CORE_TEST_PATH)
try:
known_tests = test_defs.TestDefinitions()
known_tests.Parse(core_test_path)
# read all <android root>/vendor/*/tests/testinfo/tests.xml paths
vendor_tests_pattern = os.path.join(self._root_path,
self._VENDOR_TEST_PATH)
test_file_paths = glob.glob(vendor_tests_pattern)
for test_file_path in test_file_paths:
known_tests.Parse(test_file_path)
if os.path.isfile(self._options.user_tests_file):
known_tests.Parse(self._options.user_tests_file)
return known_tests
except errors.ParseError:
raise errors.AbortError
def _DumpTests(self):
"""Prints out set of defined tests."""
print "The following tests are currently defined:"
for test in self._known_tests:
print test.GetName()
def _DoBuild(self):
logger.SilentLog("Building tests...")
target_set = Set()
for test_suite in self._GetTestsToRun():
self._AddBuildTarget(test_suite.GetBuildPath(), target_set)
if target_set:
if self._options.coverage:
self._coverage_gen.EnableCoverageBuild()
self._AddBuildTarget(self._coverage_gen.GetEmmaBuildPath(), target_set)
target_build_string = " ".join(list(target_set))
logger.Log("Building %s" % target_build_string)
cmd = 'ONE_SHOT_MAKEFILE="%s" make -C "%s" files' % (target_build_string,
self._root_path)
if not self._options.preview:
run_command.RunCommand(cmd, return_output=False)
logger.Log("Syncing to device...")
self._adb.Sync()
def _AddBuildTarget(self, build_dir, target_set):
if build_dir is not None:
build_file_path = os.path.join(build_dir, "Android.mk")
if os.path.isfile(os.path.join(self._root_path, build_file_path)):
target_set.add(build_file_path)
def _GetTestsToRun(self):
"""Get a list of TestSuite objects to run, based on command line args."""
if self._options.all_tests:
return self._known_tests.GetTests()
if self._options.continuous_tests:
return self._known_tests.GetContinuousTests()
tests = []
for name in self._test_args:
test = self._known_tests.GetTest(name)
if test is None:
logger.Log("Error: Could not find test %s" % name)
self._DumpTests()
raise errors.AbortError
tests.append(test)
return tests
def _RunTest(self, test_suite):
"""Run the provided test suite.
Builds up an adb instrument command using provided input arguments.
Args:
test_suite: TestSuite to run
"""
test_class = test_suite.GetClassName()
if self._options.test_class is not None:
test_class = self._options.test_class
if self._options.test_method is not None:
test_class = "%s#%s" % (test_class, self._options.test_method)
instrumentation_args = {}
if test_class is not None:
instrumentation_args["class"] = test_class
if self._options.wait_for_debugger:
instrumentation_args["debug"] = "true"
if self._options.suite_assign_mode:
instrumentation_args["suiteAssignment"] = "true"
if self._options.coverage:
instrumentation_args["coverage"] = "true"
if self._options.preview:
adb_cmd = self._adb.PreviewInstrumentationCommand(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
raw_mode=self._options.raw_mode,
instrumentation_args=instrumentation_args)
logger.Log(adb_cmd)
else:
self._adb.StartInstrumentationNoResults(
package_name=test_suite.GetPackageName(),
runner_name=test_suite.GetRunnerName(),
raw_mode=self._options.raw_mode,
instrumentation_args=instrumentation_args)
if self._options.coverage and test_suite.GetTargetName() is not None:
coverage_file = self._coverage_gen.ExtractReport(test_suite)
if coverage_file is not None:
logger.Log("Coverage report generated at %s" % coverage_file)
def RunTests(self):
"""Main entry method - executes the tests according to command line args."""
try:
run_command.SetAbortOnError()
self._ProcessOptions()
if self._options.only_list_tests:
self._DumpTests()
return
if not self._options.skip_build:
self._DoBuild()
for test_suite in self._GetTestsToRun():
self._RunTest(test_suite)
except KeyboardInterrupt:
logger.Log("Exiting...")
except errors.AbortError:
logger.SilentLog("Exiting due to AbortError...")
except errors.WaitForResponseTimedOutError:
logger.Log("Timed out waiting for response")
def RunTests():
runner = TestRunner()
runner.RunTests()
if __name__ == "__main__":
RunTests()

View File

@@ -87,6 +87,12 @@ These attributes map to the following commands:
coverage_target="ApiDemos" coverage_target="ApiDemos"
continuous="true" /> continuous="true" />
<test name="launchperf"
build_path="development/apps/launchperf"
package="com.android.launchperf"
class="com.android.launchperf.SimpleActivityLaunchPerformance"
coverage_target="framework" />
<!-- targeted framework tests --> <!-- targeted framework tests -->
<test name="heap" <test name="heap"
build_path="frameworks/base/tests/AndroidTests" build_path="frameworks/base/tests/AndroidTests"
@@ -114,6 +120,11 @@ These attributes map to the following commands:
class="android.content.AbstractTableMergerTest" class="android.content.AbstractTableMergerTest"
coverage_target="framework" /> coverage_target="framework" />
<test name="imf"
build_path="frameworks/base/tests/ImfTest"
package="com.android.imftest.tests"
coverage_target="framework"
continuous="true" />
<!-- selected app tests --> <!-- selected app tests -->
<test name="browser" <test name="browser"
@@ -176,6 +187,13 @@ These attributes map to the following commands:
runner=".MediaFrameworkUnitTestRunner" runner=".MediaFrameworkUnitTestRunner"
coverage_target="framework" /> coverage_target="framework" />
<test name="musicplayer"
build_path="packages/apps/Music"
package="com.android.music.tests"
runner=".MusicPlayerFunctionalTestRunner"
coverage_target="Music"
continuous="true" />
<!-- obsolete? <!-- obsolete?
<test name="mediaprov" <test name="mediaprov"
build_path="tests/MediaProvider" build_path="tests/MediaProvider"

View File

@@ -181,11 +181,14 @@ public final class AaptExecLoopTask extends Task {
task.createArg().setValue("-M"); task.createArg().setValue("-M");
task.createArg().setValue(mManifest); task.createArg().setValue(mManifest);
// resources location // resources location. This may not exists, and aapt doesn't like it, so we check first.
File res = new File(mResources);
if (res.isDirectory()) {
task.createArg().setValue("-S"); task.createArg().setValue("-S");
task.createArg().setValue(mResources); task.createArg().setValue(mResources);
}
// assets location. this may not exists, and aapt doesn't like it, so we check first. // assets location. This may not exists, and aapt doesn't like it, so we check first.
File assets = new File(mAssets); File assets = new File(mAssets);
if (assets.isDirectory()) { if (assets.isDirectory()) {
task.createArg().setValue("-A"); task.createArg().setValue("-A");

View File

@@ -27,7 +27,14 @@ import com.android.ddmlib.MultiLineReceiver;
* <p>Expects the following output: * <p>Expects the following output:
* *
* <p>If fatal error occurred when attempted to run the tests: * <p>If fatal error occurred when attempted to run the tests:
* <pre> INSTRUMENTATION_FAILED: </pre> * <pre>
* INSTRUMENTATION_STATUS: Error=error Message
* INSTRUMENTATION_FAILED:
* </pre>
* <p>or
* <pre>
* INSTRUMENTATION_RESULT: shortMsg=error Message
* </pre>
* *
* <p>Otherwise, expect a series of test results, each one containing a set of status key/value * <p>Otherwise, expect a series of test results, each one containing a set of status key/value
* pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test * pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
@@ -56,6 +63,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
private static final String CLASS = "class"; private static final String CLASS = "class";
private static final String STACK = "stack"; private static final String STACK = "stack";
private static final String NUMTESTS = "numtests"; private static final String NUMTESTS = "numtests";
private static final String ERROR = "Error";
private static final String SHORTMSG = "shortMsg";
} }
/** Test result status codes. */ /** Test result status codes. */
@@ -71,6 +80,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
private static final String STATUS = "INSTRUMENTATION_STATUS: "; private static final String STATUS = "INSTRUMENTATION_STATUS: ";
private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: "; private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: ";
private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: "; private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
private static final String CODE = "INSTRUMENTATION_CODE: ";
private static final String RESULT = "INSTRUMENTATION_RESULT: ";
private static final String TIME_REPORT = "Time: "; private static final String TIME_REPORT = "Time: ";
} }
@@ -90,6 +101,23 @@ public class InstrumentationResultParser extends MultiLineReceiver {
boolean isComplete() { boolean isComplete() {
return mCode != null && mTestName != null && mTestClass != null; return mCode != null && mTestName != null && mTestClass != null;
} }
/** Provides a more user readable string for TestResult, if possible */
@Override
public String toString() {
StringBuilder output = new StringBuilder();
if (mTestClass != null ) {
output.append(mTestClass);
output.append('#');
}
if (mTestName != null) {
output.append(mTestName);
}
if (output.length() > 0) {
return output.toString();
}
return "unknown result";
}
} }
/** Stores the status values for the test result currently being parsed */ /** Stores the status values for the test result currently being parsed */
@@ -130,6 +158,8 @@ public class InstrumentationResultParser extends MultiLineReceiver {
public void processNewLines(String[] lines) { public void processNewLines(String[] lines) {
for (String line : lines) { for (String line : lines) {
parse(line); parse(line);
// in verbose mode, dump all adb output to log
Log.v(LOG_TAG, line);
} }
} }
@@ -160,9 +190,15 @@ public class InstrumentationResultParser extends MultiLineReceiver {
// Previous status key-value has been collected. Store it. // Previous status key-value has been collected. Store it.
submitCurrentKeyValue(); submitCurrentKeyValue();
parseKey(line, Prefixes.STATUS.length()); parseKey(line, Prefixes.STATUS.length());
} else if (line.startsWith(Prefixes.STATUS_FAILED)) { } else if (line.startsWith(Prefixes.RESULT)) {
Log.e(LOG_TAG, "test run failed " + line); // Previous status key-value has been collected. Store it.
mTestListener.testRunFailed(line); submitCurrentKeyValue();
parseKey(line, Prefixes.RESULT.length());
} else if (line.startsWith(Prefixes.STATUS_FAILED) ||
line.startsWith(Prefixes.CODE)) {
// Previous status key-value has been collected. Store it.
submitCurrentKeyValue();
// just ignore the remaining data on this line
} else if (line.startsWith(Prefixes.TIME_REPORT)) { } else if (line.startsWith(Prefixes.TIME_REPORT)) {
parseTime(line, Prefixes.TIME_REPORT.length()); parseTime(line, Prefixes.TIME_REPORT.length());
} else { } else {
@@ -186,19 +222,19 @@ public class InstrumentationResultParser extends MultiLineReceiver {
if (mCurrentKey.equals(StatusKeys.CLASS)) { if (mCurrentKey.equals(StatusKeys.CLASS)) {
testInfo.mTestClass = statusValue.trim(); testInfo.mTestClass = statusValue.trim();
} } else if (mCurrentKey.equals(StatusKeys.TEST)) {
else if (mCurrentKey.equals(StatusKeys.TEST)) {
testInfo.mTestName = statusValue.trim(); testInfo.mTestName = statusValue.trim();
} } else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
try { try {
testInfo.mNumTests = Integer.parseInt(statusValue); testInfo.mNumTests = Integer.parseInt(statusValue);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue); Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
} }
} } else if (mCurrentKey.equals(StatusKeys.ERROR) ||
else if (mCurrentKey.equals(StatusKeys.STACK)) { mCurrentKey.equals(StatusKeys.SHORTMSG)) {
// test run must have failed
handleTestRunFailed(statusValue);
} else if (mCurrentKey.equals(StatusKeys.STACK)) {
testInfo.mStackTrace = statusValue; testInfo.mStackTrace = statusValue;
} }
@@ -252,8 +288,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
TestResult testInfo = getCurrentTestInfo(); TestResult testInfo = getCurrentTestInfo();
try { try {
testInfo.mCode = Integer.parseInt(value); testInfo.mCode = Integer.parseInt(value);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Expected integer status code, received: " + value); Log.e(LOG_TAG, "Expected integer status code, received: " + value);
} }
@@ -286,7 +321,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
*/ */
private void reportResult(TestResult testInfo) { private void reportResult(TestResult testInfo) {
if (!testInfo.isComplete()) { if (!testInfo.isComplete()) {
Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString()); Log.w(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
return; return;
} }
reportTestRunStarted(testInfo); reportTestRunStarted(testInfo);
@@ -337,8 +372,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
private String getTrace(TestResult testInfo) { private String getTrace(TestResult testInfo) {
if (testInfo.mStackTrace != null) { if (testInfo.mStackTrace != null) {
return testInfo.mStackTrace; return testInfo.mStackTrace;
} } else {
else {
Log.e(LOG_TAG, "Could not find stack trace for failed test "); Log.e(LOG_TAG, "Could not find stack trace for failed test ");
return new Throwable("Unknown failure").toString(); return new Throwable("Unknown failure").toString();
} }
@@ -352,12 +386,18 @@ public class InstrumentationResultParser extends MultiLineReceiver {
try { try {
float timeSeconds = Float.parseFloat(timeString); float timeSeconds = Float.parseFloat(timeString);
mTestTime = (long) (timeSeconds * 1000); mTestTime = (long) (timeSeconds * 1000);
} } catch (NumberFormatException e) {
catch (NumberFormatException e) {
Log.e(LOG_TAG, "Unexpected time format " + timeString); Log.e(LOG_TAG, "Unexpected time format " + timeString);
} }
} }
/**
* Process a instrumentation run failure
*/
private void handleTestRunFailed(String errorMsg) {
mTestListener.testRunFailed(errorMsg == null ? "Unknown error" : errorMsg);
}
/** /**
* Called by parent when adb session is complete. * Called by parent when adb session is complete.
*/ */

View File

@@ -21,26 +21,34 @@ import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log; import com.android.ddmlib.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
/** /**
* Runs a Android test command remotely and reports results. * Runs a Android test command remotely and reports results.
*/ */
public class RemoteAndroidTestRunner { public class RemoteAndroidTestRunner {
private static final char CLASS_SEPARATOR = ',';
private static final char METHOD_SEPARATOR = '#';
private static final char RUNNER_SEPARATOR = '/';
private String mClassArg;
private final String mPackageName; private final String mPackageName;
private final String mRunnerName; private final String mRunnerName;
private String mExtraArgs;
private boolean mLogOnlyMode;
private IDevice mRemoteDevice; private IDevice mRemoteDevice;
/** map of name-value instrumentation argument pairs */
private Map<String, String> mArgMap;
private InstrumentationResultParser mParser; private InstrumentationResultParser mParser;
private static final String LOG_TAG = "RemoteAndroidTest"; private static final String LOG_TAG = "RemoteAndroidTest";
private static final String DEFAULT_RUNNER_NAME = private static final String DEFAULT_RUNNER_NAME = "android.test.InstrumentationTestRunner";
"android.test.InstrumentationTestRunner";
private static final char CLASS_SEPARATOR = ',';
private static final char METHOD_SEPARATOR = '#';
private static final char RUNNER_SEPARATOR = '/';
// defined instrumentation argument names
private static final String CLASS_ARG_NAME = "class";
private static final String LOG_ARG_NAME = "log";
private static final String DEBUG_ARG_NAME = "debug";
private static final String COVERAGE_ARG_NAME = "coverage";
/** /**
* Creates a remote Android test runner. * Creates a remote Android test runner.
@@ -57,9 +65,7 @@ public class RemoteAndroidTestRunner {
mPackageName = packageName; mPackageName = packageName;
mRunnerName = runnerName; mRunnerName = runnerName;
mRemoteDevice = remoteDevice; mRemoteDevice = remoteDevice;
mClassArg = null; mArgMap = new Hashtable<String, String>();
mExtraArgs = "";
mLogOnlyMode = false;
} }
/** /**
@@ -104,7 +110,7 @@ public class RemoteAndroidTestRunner {
* @param className fully qualified class name (eg x.y.z) * @param className fully qualified class name (eg x.y.z)
*/ */
public void setClassName(String className) { public void setClassName(String className) {
mClassArg = className; addInstrumentationArg(CLASS_ARG_NAME, className);
} }
/** /**
@@ -125,7 +131,7 @@ public class RemoteAndroidTestRunner {
} }
classArgBuilder.append(classNames[i]); classArgBuilder.append(classNames[i]);
} }
mClassArg = classArgBuilder.toString(); setClassName(classArgBuilder.toString());
} }
/** /**
@@ -136,34 +142,57 @@ public class RemoteAndroidTestRunner {
* @param testName method name * @param testName method name
*/ */
public void setMethodName(String className, String testName) { public void setMethodName(String className, String testName) {
mClassArg = className + METHOD_SEPARATOR + testName; setClassName(className + METHOD_SEPARATOR + testName);
} }
/** /**
* Sets extra arguments to include in instrumentation command. * Adds a argument to include in instrumentation command.
* Must be called before 'run'. * <p/>
* Must be called before 'run'. If an argument with given name has already been provided, it's
* value will be overridden.
* *
* @param instrumentationArgs must not be null * @param name the name of the instrumentation bundle argument
* @param value the value of the argument
*/ */
public void setExtraArgs(String instrumentationArgs) { public void addInstrumentationArg(String name, String value) {
if (instrumentationArgs == null) { if (name == null || value == null) {
throw new IllegalArgumentException("instrumentationArgs cannot be null"); throw new IllegalArgumentException("name or value arguments cannot be null");
} }
mExtraArgs = instrumentationArgs; mArgMap.put(name, value);
} }
/** /**
* Returns the extra instrumentation arguments. * Adds a boolean argument to include in instrumentation command.
* <p/>
* @see RemoteAndroidTestRunner#addInstrumentationArg
*
* @param name the name of the instrumentation bundle argument
* @param value the value of the argument
*/ */
public String getExtraArgs() { public void addBooleanArg(String name, boolean value) {
return mExtraArgs; addInstrumentationArg(name, Boolean.toString(value));
} }
/** /**
* Sets this test run to log only mode - skips test execution. * Sets this test run to log only mode - skips test execution.
*/ */
public void setLogOnly(boolean logOnly) { public void setLogOnly(boolean logOnly) {
mLogOnlyMode = logOnly; addBooleanArg(LOG_ARG_NAME, logOnly);
}
/**
* Sets this debug mode of this test run. If true, the Android test runner will wait for a
* debugger to attach before proceeding with test execution.
*/
public void setDebug(boolean debug) {
addBooleanArg(DEBUG_ARG_NAME, debug);
}
/**
* Sets this code coverage mode of this test run.
*/
public void setCoverage(boolean coverage) {
addBooleanArg(COVERAGE_ARG_NAME, coverage);
} }
/** /**
@@ -172,8 +201,8 @@ public class RemoteAndroidTestRunner {
* @param listener listens for test results * @param listener listens for test results
*/ */
public void run(ITestRunListener listener) { public void run(ITestRunListener listener) {
final String runCaseCommandStr = "am instrument -w -r " final String runCaseCommandStr = String.format("am instrument -w -r %s %s",
+ getClassCmd() + " " + getLogCmd() + " " + getExtraArgs() + " " + getRunnerPath(); getArgsCommand(), getRunnerPath());
Log.d(LOG_TAG, runCaseCommandStr); Log.d(LOG_TAG, runCaseCommandStr);
mParser = new InstrumentationResultParser(listener); mParser = new InstrumentationResultParser(listener);
@@ -195,34 +224,17 @@ public class RemoteAndroidTestRunner {
} }
/** /**
* Returns the test class argument. * Returns the full instrumentation command line syntax for the provided instrumentation
* arguments.
* Returns an empty string if no arguments were specified.
*/ */
private String getClassArg() { private String getArgsCommand() {
return mClassArg; StringBuilder commandBuilder = new StringBuilder();
} for (Entry<String, String> argPair : mArgMap.entrySet()) {
final String argCmd = String.format(" -e %s %s", argPair.getKey(),
/** argPair.getValue());
* Returns the full instrumentation command which specifies the test classes to execute. commandBuilder.append(argCmd);
* Returns an empty string if no classes were specified.
*/
private String getClassCmd() {
String classArg = getClassArg();
if (classArg != null) {
return "-e class " + classArg;
}
return "";
}
/**
* Returns the full command to enable log only mode - if specified. Otherwise returns an
* empty string.
*/
private String getLogCmd() {
if (mLogOnlyMode) {
return "-e log true";
}
else {
return "";
} }
return commandBuilder.toString();
} }
} }

View File

@@ -105,7 +105,41 @@ public class InstrumentationResultParserTest extends TestCase {
} }
/** /**
* builds a common test result using TEST_NAME and TEST_CLASS. * Test basic parsing of a test run failure.
*/
public void testRunFailed() {
StringBuilder output = new StringBuilder();
final String errorMessage = "Unable to find instrumentation info";
addStatusKey(output, "Error", errorMessage);
addStatusCode(output, "-1");
output.append("INSTRUMENTATION_FAILED: com.dummy/android.test.InstrumentationTestRunner");
addLineBreak(output);
injectTestString(output.toString());
assertEquals(errorMessage, mTestResult.mRunFailedMessage);
}
/**
* Test parsing of a test run failure, where an instrumentation component failed to load
* Parsing input takes the from of INSTRUMENTATION_RESULT: fff
*/
public void testRunFailedResult() {
StringBuilder output = new StringBuilder();
final String errorMessage = "Unable to instantiate instrumentation";
output.append("INSTRUMENTATION_RESULT: shortMsg=");
output.append(errorMessage);
addLineBreak(output);
output.append("INSTRUMENTATION_CODE: 0");
addLineBreak(output);
injectTestString(output.toString());
assertEquals(errorMessage, mTestResult.mRunFailedMessage);
}
/**
* Builds a common test result using TEST_NAME and TEST_CLASS.
*/ */
private StringBuilder buildCommonResult() { private StringBuilder buildCommonResult() {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
@@ -146,6 +180,13 @@ public class InstrumentationResultParserTest extends TestCase {
outputBuilder.append(key); outputBuilder.append(key);
outputBuilder.append('='); outputBuilder.append('=');
outputBuilder.append(value); outputBuilder.append(value);
addLineBreak(outputBuilder);
}
/**
* Append line break characters to output
*/
private void addLineBreak(StringBuilder outputBuilder) {
outputBuilder.append("\r\n"); outputBuilder.append("\r\n");
} }
@@ -164,7 +205,7 @@ public class InstrumentationResultParserTest extends TestCase {
private void addStatusCode(StringBuilder outputBuilder, String value) { private void addStatusCode(StringBuilder outputBuilder, String value) {
outputBuilder.append("INSTRUMENTATION_STATUS_CODE: "); outputBuilder.append("INSTRUMENTATION_STATUS_CODE: ");
outputBuilder.append(value); outputBuilder.append(value);
outputBuilder.append("\r\n"); addLineBreak(outputBuilder);
} }
/** /**
@@ -197,11 +238,14 @@ public class InstrumentationResultParserTest extends TestCase {
TestFailure mTestStatus; TestFailure mTestStatus;
String mTrace; String mTrace;
boolean mStopped; boolean mStopped;
/** stores the error message provided to testRunFailed */
String mRunFailedMessage;
VerifyingTestResult() { VerifyingTestResult() {
mNumTestsRun = 0; mNumTestsRun = 0;
mTestStatus = null; mTestStatus = null;
mStopped = false; mStopped = false;
mRunFailedMessage = null;
} }
public void testEnded(TestIdentifier test) { public void testEnded(TestIdentifier test) {
@@ -238,8 +282,7 @@ public class InstrumentationResultParserTest extends TestCase {
} }
public void testRunFailed(String errorMessage) { public void testRunFailed(String errorMessage) {
// ignored mRunFailedMessage = errorMessage;
} }
} }
} }

View File

@@ -17,18 +17,17 @@
package com.android.ddmlib.testrunner; package com.android.ddmlib.testrunner;
import com.android.ddmlib.Client; import com.android.ddmlib.Client;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.FileListingService; import com.android.ddmlib.FileListingService;
import com.android.ddmlib.IDevice; import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver; import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.log.LogReceiver;
import com.android.ddmlib.RawImage; import com.android.ddmlib.RawImage;
import com.android.ddmlib.SyncService; import com.android.ddmlib.SyncService;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmlib.log.LogReceiver;
import junit.framework.TestCase;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import junit.framework.TestCase;
/** /**
* Tests RemoteAndroidTestRunner. * Tests RemoteAndroidTestRunner.
@@ -82,14 +81,15 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
} }
/** /**
* Test the building of the instrumentation runner command with extra args set. * Test the building of the instrumentation runner command with extra argument added.
*/ */
public void testRunWithExtraArgs() { public void testRunWithAddInstrumentationArg() {
final String extraArgs = "blah"; final String extraArgName = "blah";
mRunner.setExtraArgs(extraArgs); final String extraArgValue = "blahValue";
mRunner.addInstrumentationArg(extraArgName, extraArgValue);
mRunner.run(new EmptyListener()); mRunner.run(new EmptyListener());
assertStringsEquals(String.format("am instrument -w -r %s %s/%s", extraArgs, assertStringsEquals(String.format("am instrument -w -r -e %s %s %s/%s", extraArgName,
TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand()); extraArgValue, TEST_PACKAGE, TEST_RUNNER), mMockDevice.getLastShellCommand());
} }
@@ -243,6 +243,5 @@ public class RemoteAndroidTestRunnerTest extends TestCase {
public void testStarted(TestIdentifier test) { public void testStarted(TestIdentifier test) {
// ignore // ignore
} }
} }
} }

View File

@@ -40,7 +40,10 @@ Require-Bundle: com.android.ide.eclipse.ddms,
org.eclipse.wst.sse.ui, org.eclipse.wst.sse.ui,
org.eclipse.wst.xml.core, org.eclipse.wst.xml.core,
org.eclipse.wst.xml.ui, org.eclipse.wst.xml.ui,
org.eclipse.jdt.junit org.eclipse.jdt.junit,
org.eclipse.jdt.junit.runtime,
org.eclipse.ltk.core.refactoring,
org.eclipse.ltk.ui.refactoring
Eclipse-LazyStart: true Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt, Export-Package: com.android.ide.eclipse.adt,
com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

View File

@@ -470,7 +470,7 @@
point="org.eclipse.ui.actionSets"> point="org.eclipse.ui.actionSets">
<actionSet <actionSet
description="Android Wizards" description="Android Wizards"
id="adt.actionSet1" id="adt.actionSet.wizards"
label="Android Wizards" label="Android Wizards"
visible="true"> visible="true">
<action <action
@@ -481,12 +481,6 @@
style="push" style="push"
toolbarPath="android_project" toolbarPath="android_project"
tooltip="Opens a wizard to help create a new Android XML file"> tooltip="Opens a wizard to help create a new Android XML file">
<enablement>
<objectState
name="projectNature"
value="com.android.ide.eclipse.adt.AndroidNature">
</objectState>
</enablement>
</action> </action>
<action <action
class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction" class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
@@ -498,6 +492,21 @@
tooltip="Opens a wizard to help create a new Android project"> tooltip="Opens a wizard to help create a new Android project">
</action> </action>
</actionSet> </actionSet>
<actionSet
description="Refactorings for Android"
id="adt.actionSet.refactorings"
label="Android Refactorings"
visible="true">
<action
class="com.android.ide.eclipse.adt.refactorings.extractstring.ExtractStringAction"
definitionId="com.android.ide.eclipse.adt.refactoring.extract.string"
id="com.android.ide.eclipse.adt.actions.ExtractString"
label="Extract Android String..."
menubarPath="org.eclipse.jdt.ui.refactoring.menu/codingGroup"
style="push"
tooltip="Extracts a string into Android resource string">
</action>
</actionSet>
</extension> </extension>
<extension <extension
point="org.eclipse.debug.core.launchDelegates"> point="org.eclipse.debug.core.launchDelegates">
@@ -506,8 +515,84 @@
delegateDescription="Removes the Android JAR from the Bootstrap Classpath" delegateDescription="Removes the Android JAR from the Bootstrap Classpath"
id="com.android.ide.eclipse.adt.launch.JUnitLaunchConfigDelegate.launchAndroidJunit" id="com.android.ide.eclipse.adt.launch.JUnitLaunchConfigDelegate.launchAndroidJunit"
modes="run,debug" modes="run,debug"
name="Android JUnit" name="Android JUnit Test"
type="org.eclipse.jdt.junit.launchconfig"> type="org.eclipse.jdt.junit.launchconfig">
</launchDelegate> </launchDelegate>
</extension> </extension>
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
<launchConfigurationType
delegate="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchConfigDelegate"
id="com.android.ide.eclipse.adt.junit.launchConfigurationType"
modes="run,debug"
name="Android JUnit Test"
public="true"
sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"
sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer">
</launchConfigurationType>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTypeImages">
<launchConfigurationTypeImage
configTypeID="com.android.ide.eclipse.adt.junit.launchConfigurationType"
icon="icons/androidjunit.png"
id="com.android.ide.eclipse.adt.junit.launchConfigurationTypeImage">
</launchConfigurationTypeImage>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
<launchConfigurationTabGroup
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitTabGroup"
description="Android JUnit Test"
id="com.android.ide.eclipse.adt.junit.AndroidJUnitLaunchConfigTabGroup"
type="com.android.ide.eclipse.adt.junit.launchConfigurationType"/>
</extension>
<extension
point="org.eclipse.debug.ui.launchShortcuts">
<shortcut
class="com.android.ide.eclipse.adt.launch.junit.AndroidJUnitLaunchShortcut"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.junit.launchShortcut"
label="Android JUnit Test"
modes="run,debug">
<contextualLaunch>
<enablement>
<with variable="selection">
<count value="1"/>
<iterate>
<adapt type="org.eclipse.jdt.core.IJavaElement">
<test property="org.eclipse.jdt.core.isInJavaProjectWithNature" value="com.android.ide.eclipse.adt.AndroidNature"/>
<test property="org.eclipse.jdt.core.hasTypeOnClasspath" value="junit.framework.Test"/>
<test property="org.eclipse.jdt.junit.canLaunchAsJUnit" forcePluginActivation="true"/>
</adapt>
</iterate>
</with>
</enablement>
</contextualLaunch>
<configurationType
id="com.android.ide.eclipse.adt.junit.launchConfigurationType">
</configurationType>
</shortcut>
</extension>
<extension
point="org.eclipse.ui.commands">
<category
description="Refactorings for Android Projects"
id="com.android.ide.eclipse.adt.refactoring.category"
name="Android Refactorings">
</category>
<command
categoryId="com.android.ide.eclipse.adt.refactoring.category"
description="Extract Strings into Android String Resources"
id="com.android.ide.eclipse.adt.refactoring.extract.string"
name="Extract Android String">
</command>
</extension>
<extension
point="org.eclipse.ltk.core.refactoring.refactoringContributions">
<contribution
class="com.android.ide.eclipse.adt.refactorings.extractstring.ExtractStringContribution"
id="com.android.ide.eclipse.adt.refactoring.extract.string">
</contribution>
</extension>
</plugin> </plugin>

View File

@@ -1041,20 +1041,29 @@ public class AdtPlugin extends AbstractUIPlugin {
progress.setTaskName("Check Projects"); progress.setTaskName("Check Projects");
ArrayList<IJavaProject> list = new ArrayList<IJavaProject>();
for (IJavaProject javaProject : mPostLoadProjectsToResolve) {
if (javaProject.getProject().isOpen()) {
list.add(javaProject);
}
}
// done with this list.
mPostLoadProjectsToResolve.clear();
// check the projects that need checking. // check the projects that need checking.
// The method modifies the list (it removes the project that // The method modifies the list (it removes the project that
// do not need to be resolved again). // do not need to be resolved again).
AndroidClasspathContainerInitializer.checkProjectsCache( AndroidClasspathContainerInitializer.checkProjectsCache(
mPostLoadProjectsToCheck); mPostLoadProjectsToCheck);
mPostLoadProjectsToResolve.addAll(mPostLoadProjectsToCheck); list.addAll(mPostLoadProjectsToCheck);
// update the project that needs recompiling. // update the project that needs recompiling.
if (mPostLoadProjectsToResolve.size() > 0) { if (list.size() > 0) {
IJavaProject[] array = mPostLoadProjectsToResolve.toArray( IJavaProject[] array = list.toArray(
new IJavaProject[mPostLoadProjectsToResolve.size()]); new IJavaProject[list.size()]);
AndroidClasspathContainerInitializer.updateProjects(array); AndroidClasspathContainerInitializer.updateProjects(array);
mPostLoadProjectsToResolve.clear();
} }
progress.worked(10); progress.worked(10);
@@ -1273,7 +1282,7 @@ public class AdtPlugin extends AbstractUIPlugin {
AdtPlugin.PLUGIN_ID, AdtPlugin.PLUGIN_ID,
UNKNOWN_EDITOR); UNKNOWN_EDITOR);
try { try {
file.setPersistentProperty(qname, "1"); file.setPersistentProperty(qname, "1"); //$NON-NLS-1$
} catch (CoreException e) { } catch (CoreException e) {
// pass // pass
} }

View File

@@ -196,7 +196,7 @@ public class ApkBuilder extends BaseBuilder {
} }
// build() returns a list of project from which this project depends for future compilation. // build() returns a list of project from which this project depends for future compilation.
@SuppressWarnings("unchecked") //$NON-NLS-1$ @SuppressWarnings("unchecked")
@Override @Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException { throws CoreException {
@@ -979,7 +979,10 @@ public class ApkBuilder extends BaseBuilder {
writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list); writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list);
for (IJavaProject referencedJavaProject : referencedJavaProjects) { for (IJavaProject referencedJavaProject : referencedJavaProjects) {
if (referencedJavaProject.getProject().hasNature(AndroidConstants.NATURE)) { // only include output from non android referenced project
// (This is to handle the case of reference Android projects in the context of
// instrumentation projects that need to reference the projects to be tested).
if (referencedJavaProject.getProject().hasNature(AndroidConstants.NATURE) == false) {
writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list); writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list);
} }
} }
@@ -1084,7 +1087,10 @@ public class ApkBuilder extends BaseBuilder {
IWorkspaceRoot wsRoot = ws.getRoot(); IWorkspaceRoot wsRoot = ws.getRoot();
for (IJavaProject javaProject : referencedJavaProjects) { for (IJavaProject javaProject : referencedJavaProjects) {
if (javaProject.getProject().hasNature(AndroidConstants.NATURE)) { // only include output from non android referenced project
// (This is to handle the case of reference Android projects in the context of
// instrumentation projects that need to reference the projects to be tested).
if (javaProject.getProject().hasNature(AndroidConstants.NATURE) == false) {
// get the output folder // get the output folder
IPath path = null; IPath path = null;
try { try {

View File

@@ -197,7 +197,7 @@ public class PreCompilerBuilder extends BaseBuilder {
} }
// build() returns a list of project from which this project depends for future compilation. // build() returns a list of project from which this project depends for future compilation.
@SuppressWarnings("unchecked") //$NON-NLS-1$ @SuppressWarnings("unchecked")
@Override @Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException { throws CoreException {
@@ -222,6 +222,7 @@ public class PreCompilerBuilder extends BaseBuilder {
PreCompilerDeltaVisitor dv = null; PreCompilerDeltaVisitor dv = null;
String javaPackage = null; String javaPackage = null;
int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
if (kind == FULL_BUILD) { if (kind == FULL_BUILD) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
@@ -253,6 +254,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// get the java package from the visitor // get the java package from the visitor
javaPackage = dv.getManifestPackage(); javaPackage = dv.getManifestPackage();
minSdkVersion = dv.getMinSdkVersion();
} }
} }
@@ -276,7 +278,7 @@ public class PreCompilerBuilder extends BaseBuilder {
if (manifest == null) { if (manifest == null) {
String msg = String.format(Messages.s_File_Missing, String msg = String.format(Messages.s_File_Missing,
AndroidConstants.FN_ANDROID_MANIFEST); AndroidConstants.FN_ANDROID_MANIFEST);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); AdtPlugin.printErrorToConsole(project, msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run. // This interrupts the build. The next builders will not run.
@@ -304,14 +306,29 @@ public class PreCompilerBuilder extends BaseBuilder {
// get the java package from the parser // get the java package from the parser
javaPackage = parser.getPackage(); javaPackage = parser.getPackage();
minSdkVersion = parser.getApiLevelRequirement();
}
if (minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK &&
minSdkVersion < projectTarget.getApiVersionNumber()) {
// check it against the target api level
String msg = String.format(
"Manifest min SDK version (%1$d) is lower than project target API level (%2$d)",
minSdkVersion, projectTarget.getApiVersionNumber());
AdtPlugin.printErrorToConsole(project, msg);
BaseProjectHelper.addMarker(manifest, AdtConstants.MARKER_ADT, msg,
IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run.
stopBuild(msg);
} }
if (javaPackage == null || javaPackage.length() == 0) { if (javaPackage == null || javaPackage.length() == 0) {
// looks like the AndroidManifest file isn't valid. // looks like the AndroidManifest file isn't valid.
String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
AndroidConstants.FN_ANDROID_MANIFEST); AndroidConstants.FN_ANDROID_MANIFEST);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, AdtPlugin.printErrorToConsole(project, msg);
msg); markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// This interrupts the build. The next builders will not run. // This interrupts the build. The next builders will not run.
stopBuild(msg); stopBuild(msg);

View File

@@ -72,8 +72,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
/** Manifest check/parsing flag. */ /** Manifest check/parsing flag. */
private boolean mCheckedManifestXml = false; private boolean mCheckedManifestXml = false;
/** Application Pacakge, gathered from the parsing of the manifest */ /** Application Package, gathered from the parsing of the manifest */
private String mJavaPackage = null; private String mJavaPackage = null;
/** minSDKVersion attribute value, gathered from the parsing of the manifest */
private int mMinSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
// Internal usage fields. // Internal usage fields.
/** /**
@@ -137,6 +139,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
return mJavaPackage; return mJavaPackage;
} }
/**
* Returns the minSDkVersion attribute from the manifest if it was checked/parsed.
* <p/>
* This can return {@link AndroidManifestParser#INVALID_MIN_SDK} in two cases:
* <ul>
* <li>The manifest was not part of the resource change delta, and the manifest was
* not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
* <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
* but the package declaration is missing</li>
* </ul>
* @return the minSdkVersion or {@link AndroidManifestParser#INVALID_MIN_SDK}.
*/
public int getMinSdkVersion() {
return mMinSdkVersion;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
@@ -184,6 +202,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
if (parser != null) { if (parser != null) {
mJavaPackage = parser.getPackage(); mJavaPackage = parser.getPackage();
mMinSdkVersion = parser.getApiLevelRequirement();
} }
mCheckedManifestXml = true; mCheckedManifestXml = true;

View File

@@ -55,7 +55,7 @@ public class ResourceManagerBuilder extends BaseBuilder {
} }
// build() returns a list of project from which this project depends for future compilation. // build() returns a list of project from which this project depends for future compilation.
@SuppressWarnings("unchecked") //$NON-NLS-1$ @SuppressWarnings("unchecked")
@Override @Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor) protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException { throws CoreException {

View File

@@ -32,16 +32,41 @@ public class AndroidLaunchConfiguration {
*/ */
public int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; public int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
public static final boolean AUTO_TARGET_MODE = true; /**
* Target selection mode for the configuration: either {@link #AUTO} or {@link #MANUAL}.
*/
public enum TargetMode {
/** Automatic target selection mode. */
AUTO(true),
/** Manual target selection mode. */
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. * Target selection mode.
* <ul> * @see TargetMode
* <li><code>true</code>: automatic mode, see {@link #AUTO_TARGET_MODE}</li>
* <li><code>false</code>: manual mode</li>
* </ul>
*/ */
public boolean mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE; public TargetMode mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE;
/** /**
* Indicates whether the emulator should be called with -wipe-data * Indicates whether the emulator should be called with -wipe-data
@@ -81,8 +106,9 @@ public class AndroidLaunchConfiguration {
} }
try { try {
mTargetMode = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, boolean value = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
mTargetMode); mTargetMode.getValue());
mTargetMode = TargetMode.getMode(value);
} catch (CoreException e) { } catch (CoreException e) {
// nothing to be done here, we'll use the default value // nothing to be done here, we'll use the default value
} }

View File

@@ -17,9 +17,6 @@
package com.android.ide.eclipse.adt.launch; package com.android.ide.eclipse.adt.launch;
import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.Client; import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData; import com.android.ddmlib.ClientData;
import com.android.ddmlib.Device; import com.android.ddmlib.Device;
@@ -27,13 +24,19 @@ import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log; import com.android.ddmlib.Log;
import com.android.ddmlib.MultiLineReceiver; import com.android.ddmlib.MultiLineReceiver;
import com.android.ddmlib.SyncService; import com.android.ddmlib.SyncService;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.SyncService.SyncResult; import com.android.ddmlib.SyncService.SyncResult;
import com.android.ide.eclipse.adt.AdtPlugin; 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.DelayedLaunchInfo.InstallRetryMode;
import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse; import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse;
import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager; import com.android.sdklib.SdkManager;
import com.android.sdklib.avd.AvdManager; import com.android.sdklib.avd.AvdManager;
@@ -52,6 +55,8 @@ import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.ui.DebugUITools; import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IVMConnector; import org.eclipse.jdt.launching.IVMConnector;
import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.JavaRuntime;
@@ -64,6 +69,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -94,8 +100,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/** /**
* List of {@link DelayedLaunchInfo} waiting for an emulator to connect. * List of {@link DelayedLaunchInfo} waiting for an emulator to connect.
* <p>Once an emulator has connected, {@link DelayedLaunchInfo#mDevice} is set and the * <p>Once an emulator has connected, {@link DelayedLaunchInfo#getDevice()} is set and the
* DelayedLaunchInfo object is moved to {@link AndroidLaunchController#mWaitingForReadyEmulatorList}. * DelayedLaunchInfo object is moved to
* {@link AndroidLaunchController#mWaitingForReadyEmulatorList}.
* <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b> * <b>ALL ACCESS MUST BE INSIDE A <code>synchronized (sListLock)</code> block!</b>
*/ */
private final ArrayList<DelayedLaunchInfo> mWaitingForEmulatorLaunches = private final ArrayList<DelayedLaunchInfo> mWaitingForEmulatorLaunches =
@@ -236,7 +243,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// set default target mode // set default target mode
wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
LaunchConfigDelegate.DEFAULT_TARGET_MODE); LaunchConfigDelegate.DEFAULT_TARGET_MODE.getValue());
// default AVD: None // default AVD: None
wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String) null); wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String) null);
@@ -307,7 +314,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* <code>DEBUG_MODE</code>. * <code>DEBUG_MODE</code>.
* @param apk the resource to the apk to launch. * @param apk the resource to the apk to launch.
* @param debuggable the debuggable value of the app, or null if not set. * @param debuggable the debuggable value of the app, or null if not set.
* @param requiredApiVersionNumber the api version required by the app, or -1 if none. * @param requiredApiVersionNumber the api version required by the app, or
* {@link AndroidManifestParser#INVALID_MIN_SDK} if none.
* @param launchAction the action to perform after app sync * @param launchAction the action to perform after app sync
* @param config the launch configuration * @param config the launch configuration
* @param launch the launch object * @param launch the launch object
@@ -331,6 +339,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
Sdk currentSdk = Sdk.getCurrent(); Sdk currentSdk = Sdk.getCurrent();
AvdManager avdManager = currentSdk.getAvdManager(); 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 // get the project target
final IAndroidTarget projectTarget = currentSdk.getTarget(project); final IAndroidTarget projectTarget = currentSdk.getTarget(project);
@@ -355,7 +373,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* If == 1, launch the application on this AVD/device. * 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 // if we are in automatic target mode, we need to find the current devices
IDevice[] devices = AndroidDebugBridge.getBridge().getDevices(); IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
@@ -468,7 +486,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// FIXME: ask the user if he wants to create a AVD. // FIXME: ask the user if he wants to create a AVD.
// we found no compatible AVD. // we found no compatible AVD.
AdtPlugin.printErrorToConsole(project, String.format( AdtPlugin.printErrorToConsole(project, String.format(
"Failed to find a AVD compatible with target '%1$s'. Launch aborted.", "Failed to find an AVD compatible with target '%1$s'. Launch aborted.",
projectTarget.getName())); projectTarget.getName()));
stopLaunch(launchInfo); stopLaunch(launchInfo);
return; return;
@@ -638,20 +656,21 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION); String deviceApiVersionName = device.getProperty(IDevice.PROP_BUILD_VERSION);
String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER); String value = device.getProperty(IDevice.PROP_BUILD_VERSION_NUMBER);
int deviceApiVersionNumber = 0; int deviceApiVersionNumber = AndroidManifestParser.INVALID_MIN_SDK;
try { try {
deviceApiVersionNumber = Integer.parseInt(value); deviceApiVersionNumber = Integer.parseInt(value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// pass, we'll keep the deviceVersionNumber value at 0. // pass, we'll keep the deviceVersionNumber value at 0.
} }
if (launchInfo.getRequiredApiVersionNumber() == 0) { if (launchInfo.getRequiredApiVersionNumber() == AndroidManifestParser.INVALID_MIN_SDK) {
// warn the API level requirement is not set. // warn the API level requirement is not set.
AdtPlugin.printErrorToConsole(launchInfo.getProject(), AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Application does not specify an API level requirement!"); "WARNING: Application does not specify an API level requirement!");
// and display the target device API level (if known) // and display the target device API level (if known)
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { if (deviceApiVersionName == null ||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printErrorToConsole(launchInfo.getProject(), AdtPlugin.printErrorToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!"); "WARNING: Unknown device API version!");
} else { } else {
@@ -660,7 +679,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
deviceApiVersionName)); deviceApiVersionName));
} }
} else { // app requires a specific API level } else { // app requires a specific API level
if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { if (deviceApiVersionName == null ||
deviceApiVersionNumber == AndroidManifestParser.INVALID_MIN_SDK) {
AdtPlugin.printToConsole(launchInfo.getProject(), AdtPlugin.printToConsole(launchInfo.getProject(),
"WARNING: Unknown device API version!"); "WARNING: Unknown device API version!");
} else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) { } else if (deviceApiVersionNumber < launchInfo.getRequiredApiVersionNumber()) {
@@ -792,6 +812,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
return false; 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; return installResult;
} }
@@ -803,6 +831,81 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
return false; 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<DelayedLaunchInfo> getDependenciesLaunchInfo(DelayedLaunchInfo launchInfo) {
List<DelayedLaunchInfo> dependencies = new ArrayList<DelayedLaunchInfo>();
// 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<IJavaProject> 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. * Installs the application package that was pushed to a temporary location on the device.
* @param launchInfo The launch information * @param launchInfo The launch information

View File

@@ -16,12 +16,13 @@
package com.android.ide.eclipse.adt.launch; package com.android.ide.eclipse.adt.launch;
import com.android.ddmlib.IDevice;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IProgressMonitor;
import com.android.ddmlib.IDevice;
/** /**
* A delayed launch waiting for a device to be present or ready before the * A delayed launch waiting for a device to be present or ready before the
* application is launched. * application is launched.
@@ -50,7 +51,8 @@ public final class DelayedLaunchInfo {
/** debuggable attribute of the manifest file. */ /** debuggable attribute of the manifest file. */
private final Boolean mDebuggable; private final Boolean mDebuggable;
/** Required ApiVersionNumber by the app. 0 means no requirements */ /** Required ApiVersionNumber by the app. {@link AndroidManifestParser#INVALID_MIN_SDK} means
* no requirements */
private final int mRequiredApiVersionNumber; private final int mRequiredApiVersionNumber;
private InstallRetryMode mRetryMode = InstallRetryMode.NEVER; private InstallRetryMode mRetryMode = InstallRetryMode.NEVER;
@@ -81,7 +83,8 @@ public final class DelayedLaunchInfo {
* @param launchAction action to perform after app install * @param launchAction action to perform after app install
* @param pack IFile to the package (.apk) file * @param pack IFile to the package (.apk) file
* @param debuggable debuggable attribute of the app's manifest file. * @param debuggable debuggable attribute of the app's manifest file.
* @param requiredApiVersionNumber required SDK version by the app. 0 means no requirements. * @param requiredApiVersionNumber required SDK version by the app.
* {@link AndroidManifestParser#INVALID_MIN_SDK} means no requirements.
* @param launch the launch object * @param launch the launch object
* @param monitor progress monitor for launch * @param monitor progress monitor for launch
*/ */

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.launch; package com.android.ide.eclipse.adt.launch;
import com.android.ide.eclipse.adt.AdtPlugin; 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.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.ide.eclipse.ddms.DdmsPlugin;
@@ -292,14 +293,15 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
public void initializeFrom(ILaunchConfiguration configuration) { public void initializeFrom(ILaunchConfiguration configuration) {
AvdManager avdManager = Sdk.getCurrent().getAvdManager(); AvdManager avdManager = Sdk.getCurrent().getAvdManager();
boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic TargetMode mode = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic
try { try {
value = configuration.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, value); mode = TargetMode.getMode(configuration.getAttribute(
LaunchConfigDelegate.ATTR_TARGET_MODE, mode.getValue()));
} catch (CoreException e) { } catch (CoreException e) {
// let's not do anything here, we'll use the default value // let's not do anything here, we'll use the default value
} }
mAutoTargetButton.setSelection(value); mAutoTargetButton.setSelection(mode.getValue());
mManualTargetButton.setSelection(!value); mManualTargetButton.setSelection(!mode.getValue());
// look for the project name to get its target. // look for the project name to get its target.
String stringValue = ""; String stringValue = "";
@@ -354,7 +356,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
mPreferredAvdSelector.setSelection(null); mPreferredAvdSelector.setSelection(null);
} }
value = LaunchConfigDelegate.DEFAULT_WIPE_DATA; boolean value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
try { try {
value = configuration.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, value); value = configuration.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, value);
} catch (CoreException e) { } catch (CoreException e) {
@@ -440,7 +442,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
*/ */
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
LaunchConfigDelegate.DEFAULT_TARGET_MODE); LaunchConfigDelegate.DEFAULT_TARGET_MODE.getValue());
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
LaunchConfigDelegate.DEFAULT_SPEED); LaunchConfigDelegate.DEFAULT_SPEED);
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,

View File

@@ -18,15 +18,14 @@ package com.android.ide.eclipse.adt.launch;
import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.AndroidDebugBridge;
import com.android.ide.eclipse.adt.AdtPlugin; 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.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.CoreException;
@@ -51,7 +50,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
/** Target mode parameters: true is automatic, false is manual */ /** 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 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: * Launch action:
@@ -152,7 +151,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
AdtPlugin.printToConsole(project, "Android Launch!"); AdtPlugin.printToConsole(project, "Android Launch!");
// check if the project is using the proper sdk. // 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) { if (checkAndroidProject(project) == false) {
AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!"); AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!");
androidLaunch.stopLaunch(); androidLaunch.stopLaunch();
@@ -215,7 +214,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
AndroidLaunchController controller = AndroidLaunchController.getInstance(); AndroidLaunchController controller = AndroidLaunchController.getInstance();
// get the application package // get the application package
IFile applicationPackage = getApplicationPackage(project); IFile applicationPackage = ProjectHelper.getApplicationPackage(project);
if (applicationPackage == null) { if (applicationPackage == null) {
androidLaunch.stopLaunch(); androidLaunch.stopLaunch();
return; 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. * Returns the name of the activity.
*/ */

View File

@@ -55,6 +55,11 @@ import org.eclipse.swt.widgets.Text;
*/ */
public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
/**
*
*/
public static final String LAUNCH_TAB_IMAGE = "mainLaunchTab.png"; //$NON-NLS-1$
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
protected Text mProjText; protected Text mProjText;
@@ -194,7 +199,7 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
@Override @Override
public Image getImage() { public Image getImage() {
return AdtPlugin.getImageLoader().loadImage("mainLaunchTab.png", null); return AdtPlugin.getImageLoader().loadImage(LAUNCH_TAB_IMAGE, null);
} }
@@ -310,21 +315,8 @@ public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
} }
mProjText.setText(projectName); mProjText.setText(projectName);
// get the list of projects IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null);
if (projects != null) {
// look for the currently selected project
IProject proj = null;
for (IJavaProject p : projects) {
if (p.getElementName().equals(projectName)) {
proj = p.getProject();
break;
}
}
loadActivities(proj); loadActivities(proj);
}
// load the launch action. // load the launch action.
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;

View File

@@ -0,0 +1,276 @@
/*
* 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.launch.junit;
import com.android.ddmlib.IDevice;
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 org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IStreamsProxy;
import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate;
import org.eclipse.jdt.launching.IVMRunner;
import org.eclipse.jdt.launching.VMRunnerConfiguration;
/**
* A launch action that executes a instrumentation test run on an Android device.
*/
class AndroidJUnitLaunchAction implements IAndroidLaunchAction {
private String mTestPackage;
private String mRunner;
/**
* Creates a AndroidJUnitLaunchAction.
*
* @param testPackage the Android application package that contains the tests to run
* @param runner the InstrumentationTestRunner that will execute the tests
*/
public AndroidJUnitLaunchAction(String testPackage, String runner) {
mTestPackage = testPackage;
mRunner = runner;
}
/**
* Launch a instrumentation test run on given Android device.
* Reuses JDT JUnit launch delegate so results can be communicated back to JDT JUnit UI.
*
* @see IAndroidLaunchAction#doLaunchAction(DelayedLaunchInfo, IDevice)
*/
public boolean doLaunchAction(DelayedLaunchInfo info, IDevice device) {
String msg = String.format("Launching instrumentation %s on device %s", mRunner,
device.getSerialNumber());
AdtPlugin.printToConsole(info.getProject(), msg);
try {
JUnitLaunchDelegate junitDelegate = new JUnitLaunchDelegate(info, device);
final String mode = info.isDebugMode() ? ILaunchManager.DEBUG_MODE :
ILaunchManager.RUN_MODE;
junitDelegate.launch(info.getLaunch().getLaunchConfiguration(), mode, info.getLaunch(),
info.getMonitor());
// TODO: need to add AMReceiver-type functionality somewhere
} catch (CoreException e) {
AdtPlugin.printErrorToConsole(info.getProject(), "Failed to launch test");
}
return true;
}
/**
* {@inheritDoc}
*/
public String getLaunchDescription() {
return String.format("%s JUnit launch", mRunner);
}
/**
* Extends the JDT JUnit launch delegate to allow for JUnit UI reuse.
*/
private class JUnitLaunchDelegate extends JUnitLaunchConfigurationDelegate {
private IDevice mDevice;
private DelayedLaunchInfo mLaunchInfo;
public JUnitLaunchDelegate(DelayedLaunchInfo info, IDevice device) {
mLaunchInfo = info;
mDevice = device;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String, org.eclipse.debug.core.ILaunch, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public synchronized void launch(ILaunchConfiguration configuration, String mode,
ILaunch launch, IProgressMonitor monitor) throws CoreException {
// TODO: is progress monitor adjustment needed here?
super.launch(configuration, mode, launch, monitor);
}
/**
* {@inheritDoc}
* @throws CoreException
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#verifyMainTypeName(org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
public String verifyMainTypeName(ILaunchConfiguration configuration) throws CoreException {
return "com.android.ide.eclipse.adt.junit.internal.runner.RemoteAndroidTestRunner"; //$NON-NLS-1$
}
/**
* Overrides parent to return a VM Runner implementation which launches a thread, rather
* than a separate VM process
* @throws CoreException
*/
@Override
public IVMRunner getVMRunner(ILaunchConfiguration configuration, String mode)
throws CoreException {
return new VMTestRunner(new AndroidJUnitLaunchInfo(mLaunchInfo.getProject(),
mTestPackage, mRunner, mLaunchInfo.isDebugMode(), mDevice));
}
/**
* {@inheritDoc}
* @throws CoreException
* @see org.eclipse.debug.core.model.LaunchConfigurationDelegate#getLaunch(org.eclipse.debug.core.ILaunchConfiguration, java.lang.String)
*/
@Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode)
throws CoreException {
return mLaunchInfo.getLaunch();
}
}
/**
* Provides a VM runner implementation which starts a thread implementation of a launch process
*/
private static class VMTestRunner implements IVMRunner {
private final AndroidJUnitLaunchInfo mJUnitInfo;
VMTestRunner(AndroidJUnitLaunchInfo info) {
mJUnitInfo = info;
}
/**
* {@inheritDoc}
* @throws CoreException
*/
public void run(final VMRunnerConfiguration config, ILaunch launch,
IProgressMonitor monitor) throws CoreException {
TestRunnerProcess runnerProcess =
new TestRunnerProcess(config, launch, mJUnitInfo);
runnerProcess.start();
launch.addProcess(runnerProcess);
}
}
/**
* Launch process that executes the tests.
*/
private static class TestRunnerProcess extends Thread implements IProcess {
private final VMRunnerConfiguration mRunConfig;
private final ILaunch mLaunch;
private final AndroidJUnitLaunchInfo mJUnitInfo;
private RemoteAdtTestRunner mTestRunner = null;
private boolean mIsTerminated = false;
TestRunnerProcess(VMRunnerConfiguration runConfig, ILaunch launch,
AndroidJUnitLaunchInfo info) {
mRunConfig = runConfig;
mLaunch = launch;
mJUnitInfo = info;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getAttribute(java.lang.String)
*/
public String getAttribute(String key) {
return null;
}
/**
* {@inheritDoc}
* @throws DebugException
* @see org.eclipse.debug.core.model.IProcess#getExitValue()
*/
public int getExitValue() throws DebugException {
return 0;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getLabel()
*/
public String getLabel() {
return mLaunch.getLaunchMode();
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getLaunch()
*/
public ILaunch getLaunch() {
return mLaunch;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#getStreamsProxy()
*/
public IStreamsProxy getStreamsProxy() {
return null;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.IProcess#setAttribute(java.lang.String,
* java.lang.String)
*/
public void setAttribute(String key, String value) {
// ignore
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
@SuppressWarnings("unchecked")
public Object getAdapter(Class adapter) {
return null;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#canTerminate()
*/
public boolean canTerminate() {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.debug.core.model.ITerminate#isTerminated()
*/
public boolean isTerminated() {
return mIsTerminated;
}
/**
* {@inheritDoc}
* @throws DebugException
* @see org.eclipse.debug.core.model.ITerminate#terminate()
*/
public void terminate() throws DebugException {
if (mTestRunner != null) {
mTestRunner.terminate();
}
mIsTerminated = true;
}
/**
* Launches a test runner that will communicate results back to JDT JUnit UI
*/
@Override
public void run() {
mTestRunner = new RemoteAdtTestRunner();
mTestRunner.runTests(mRunConfig.getProgramArguments(), mJUnitInfo);
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.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.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.IProject;
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.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
/**
* Run configuration that can execute JUnit tests on an Android platform.
* <p/>
* Will deploy apps on target Android platform by reusing functionality from ADT
* LaunchConfigDelegate, and then run JUnits tests by reusing functionality from JDT
* JUnitLaunchConfigDelegate.
*/
@SuppressWarnings("restriction")
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$
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
@Override
protected void doLaunch(final ILaunchConfiguration configuration, final String mode,
IProgressMonitor monitor, IProject project, final AndroidLaunch androidLaunch,
AndroidLaunchConfiguration config, AndroidLaunchController controller,
IFile applicationPackage, AndroidManifestParser manifestParser) {
String testPackage = manifestParser.getPackage();
String runner = getRunner(project, configuration, manifestParser);
if (runner == null) {
AdtPlugin.displayError("Android Launch",
"An instrumention test runner is not specified!");
androidLaunch.stopLaunch();
return;
}
IAndroidLaunchAction junitLaunch = new AndroidJUnitLaunchAction(testPackage, runner);
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
junitLaunch, config, androidLaunch, monitor);
}
/**
* Gets a instrumentation runner for the launch.
* <p/>
* If a runner is stored in the given <code>configuration</code>, will return that.
* Otherwise, will try to find the first valid runner for the project.
* If a runner can still not be found, will return <code>null</code>.
*
* @param project the {@link IProject} for the app
* @param configuration the {@link ILaunchConfiguration} for the launch
* @param manifestParser the {@link AndroidManifestParser} for the project
*
* @return <code>null</code> if no instrumentation runner can be found, otherwise return
* the fully qualified runner name.
*/
private String getRunner(IProject project, ILaunchConfiguration configuration,
AndroidManifestParser manifestParser) {
try {
String runner = getRunnerFromConfig(configuration);
if (runner != null) {
return runner;
}
final InstrumentationRunnerValidator instrFinder = new InstrumentationRunnerValidator(
BaseProjectHelper.getJavaProject(project), manifestParser);
runner = instrFinder.getValidInstrumentationTestRunner();
if (runner != null) {
AdtPlugin.printErrorToConsole(project,
String.format("Warning: No instrumentation runner found for the launch, " +
"using %1$s", runner));
return runner;
}
AdtPlugin.printErrorToConsole(project,
String.format("ERROR: Application does not specify a %1$s instrumentation or does not declare uses-library %2$s",
AndroidConstants.CLASS_INSTRUMENTATION_RUNNER,
AndroidConstants.LIBRARY_TEST_RUNNER));
return null;
} catch (CoreException e) {
AdtPlugin.log(e, "Error when retrieving instrumentation info"); //$NON-NLS-1$
}
return null;
}
private String getRunnerFromConfig(ILaunchConfiguration configuration) throws CoreException {
String runner = configuration.getAttribute(ATTR_INSTR_NAME, EMPTY_STRING);
if (runner.length() < 1) {
return null;
}
return runner;
}
/**
* Helper method to set JUnit-related attributes expected by JDT JUnit runner
*
* @param config the launch configuration to modify
*/
static void setJUnitDefaults(ILaunchConfigurationWorkingCopy config) {
// set the test runner to JUnit3 to placate JDT JUnit runner logic
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
TestKindRegistry.JUNIT3_TEST_KIND_ID);
}
}

View File

@@ -0,0 +1,977 @@
/*
* 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.launch.junit;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.launch.MainLaunchConfigTab;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.JUnitMigrationDelegate;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.internal.junit.launcher.TestSelectionDialog;
import org.eclipse.jdt.internal.junit.ui.JUnitMessages;
import org.eclipse.jdt.internal.junit.util.LayoutUtil;
import org.eclipse.jdt.internal.junit.util.TestSearchEngine;
import org.eclipse.jdt.internal.ui.wizards.TypedElementSelectionValidator;
import org.eclipse.jdt.internal.ui.wizards.TypedViewerFilter;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.ui.JavaElementComparator;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jdt.ui.StandardJavaElementContentProvider;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.SelectionDialog;
import java.lang.reflect.InvocationTargetException;
/**
* The launch config UI tab for Android JUnit
* <p/>
* Based on org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationTab
*/
@SuppressWarnings("restriction")
public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurationTab {
// Project UI widgets
private Label mProjLabel;
private Text mProjText;
private Button mProjButton;
// Test class UI widgets
private Text mTestText;
private Button mSearchButton;
private String mOriginalTestMethodName;
private Label mTestMethodLabel;
private Text mContainerText;
private IJavaElement mContainerElement;
private final ILabelProvider mJavaElementLabelProvider = new JavaElementLabelProvider();
private Button mContainerSearchButton;
private Button mTestContainerRadioButton;
private Button mTestRadioButton;
private Label mTestLabel;
// Android specific members
private Image mTabIcon = null;
private Combo mInstrumentationCombo;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final String TAG = "AndroidJUnitLaunchConfigurationTab"; //$NON-NLS-1$
private String[] mInstrumentations = null;
private InstrumentationRunnerValidator mInstrValidator = null;
private ProjectChooserHelper mProjectChooserHelper;
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
Composite comp = new Composite(parent, SWT.NONE);
setControl(comp);
GridLayout topLayout = new GridLayout();
topLayout.numColumns = 3;
comp.setLayout(topLayout);
createSingleTestSection(comp);
createTestContainerSelectionGroup(comp);
createSpacer(comp);
createInstrumentationGroup(comp);
createSpacer(comp);
Dialog.applyDialogFont(comp);
// TODO: add help link here when available
//PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(),
// IJUnitHelpContextIds.LAUNCH_CONFIGURATION_DIALOG_JUNIT_MAIN_TAB);
validatePage();
}
private void createSpacer(Composite comp) {
Label label = new Label(comp, SWT.NONE);
GridData gd = new GridData();
gd.horizontalSpan = 3;
label.setLayoutData(gd);
}
private void createSingleTestSection(Composite comp) {
mTestRadioButton = new Button(comp, SWT.RADIO);
mTestRadioButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_oneTest);
GridData gd = new GridData();
gd.horizontalSpan = 3;
mTestRadioButton.setLayoutData(gd);
mTestRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (mTestRadioButton.getSelection()) {
testModeChanged();
}
}
});
mProjLabel = new Label(comp, SWT.NONE);
mProjLabel.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_project);
gd = new GridData();
gd.horizontalIndent = 25;
mProjLabel.setLayoutData(gd);
mProjText = new Text(comp, SWT.SINGLE | SWT.BORDER);
mProjText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mProjText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent evt) {
validatePage();
updateLaunchConfigurationDialog();
mSearchButton.setEnabled(mTestRadioButton.getSelection() &&
mProjText.getText().length() > 0);
}
});
mProjButton = new Button(comp, SWT.PUSH);
mProjButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_browse);
mProjButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt) {
handleProjectButtonSelected();
}
});
setButtonGridData(mProjButton);
mTestLabel = new Label(comp, SWT.NONE);
gd = new GridData();
gd.horizontalIndent = 25;
mTestLabel.setLayoutData(gd);
mTestLabel.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_test);
mTestText = new Text(comp, SWT.SINGLE | SWT.BORDER);
mTestText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mTestText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent evt) {
validatePage();
updateLaunchConfigurationDialog();
}
});
mSearchButton = new Button(comp, SWT.PUSH);
mSearchButton.setEnabled(mProjText.getText().length() > 0);
mSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search);
mSearchButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt) {
handleSearchButtonSelected();
}
});
setButtonGridData(mSearchButton);
new Label(comp, SWT.NONE);
mTestMethodLabel = new Label(comp, SWT.NONE);
mTestMethodLabel.setText(""); //$NON-NLS-1$
gd = new GridData();
gd.horizontalSpan = 2;
mTestMethodLabel.setLayoutData(gd);
}
private void createTestContainerSelectionGroup(Composite comp) {
mTestContainerRadioButton = new Button(comp, SWT.RADIO);
mTestContainerRadioButton.setText(
JUnitMessages.JUnitLaunchConfigurationTab_label_containerTest);
GridData gd = new GridData();
gd.horizontalSpan = 3;
mTestContainerRadioButton.setLayoutData(gd);
mTestContainerRadioButton.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (mTestContainerRadioButton.getSelection()) {
testModeChanged();
}
}
public void widgetDefaultSelected(SelectionEvent e) {
}
});
mContainerText = new Text(comp, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalIndent = 25;
gd.horizontalSpan = 2;
mContainerText.setLayoutData(gd);
mContainerText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent evt) {
updateLaunchConfigurationDialog();
}
});
mContainerSearchButton = new Button(comp, SWT.PUSH);
mContainerSearchButton.setText(JUnitMessages.JUnitLaunchConfigurationTab_label_search);
mContainerSearchButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent evt) {
handleContainerSearchButtonSelected();
}
});
setButtonGridData(mContainerSearchButton);
}
private void createInstrumentationGroup(Composite comp) {
Label loaderLabel = new Label(comp, SWT.NONE);
loaderLabel.setText("Instrumentation runner:");
GridData gd = new GridData();
gd.horizontalIndent = 0;
loaderLabel.setLayoutData(gd);
mInstrumentationCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY);
gd = new GridData(GridData.FILL_HORIZONTAL);
mInstrumentationCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mInstrumentationCombo.clearSelection();
mInstrumentationCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
validatePage();
updateLaunchConfigurationDialog();
}
});
}
private void handleContainerSearchButtonSelected() {
IJavaElement javaElement = chooseContainer(mContainerElement);
if (javaElement != null) {
setContainerElement(javaElement);
}
}
private void setContainerElement(IJavaElement javaElement) {
mContainerElement = javaElement;
mContainerText.setText(getPresentationName(javaElement));
validatePage();
updateLaunchConfigurationDialog();
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
*/
public void initializeFrom(ILaunchConfiguration config) {
String projectName = updateProjectFromConfig(config);
String containerHandle = EMPTY_STRING;
try {
containerHandle = config.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, EMPTY_STRING);
} catch (CoreException ce) {
// ignore
}
if (containerHandle.length() > 0) {
updateTestContainerFromConfig(config);
} else {
updateTestTypeFromConfig(config);
}
IProject proj = mProjectChooserHelper.getAndroidProject(projectName);
loadInstrumentations(proj);
updateInstrumentationFromConfig(config);
validatePage();
}
private void updateInstrumentationFromConfig(ILaunchConfiguration config) {
boolean found = false;
try {
String currentInstrumentation = config.getAttribute(
AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME, EMPTY_STRING);
if (mInstrumentations != null) {
// look for the name of the instrumentation in the combo.
for (int i = 0; i < mInstrumentations.length; i++) {
if (currentInstrumentation.equals(mInstrumentations[i])) {
found = true;
mInstrumentationCombo.select(i);
break;
}
}
}
} catch (CoreException ce) {
// ignore
}
if (!found) {
mInstrumentationCombo.clearSelection();
}
}
private String updateProjectFromConfig(ILaunchConfiguration config) {
String projectName = EMPTY_STRING;
try {
projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
EMPTY_STRING);
} catch (CoreException ce) {
// ignore
}
mProjText.setText(projectName);
return projectName;
}
private void updateTestTypeFromConfig(ILaunchConfiguration config) {
String testTypeName = EMPTY_STRING;
mOriginalTestMethodName = EMPTY_STRING;
try {
testTypeName = config.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, ""); //$NON-NLS-1$
mOriginalTestMethodName = config.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME, ""); //$NON-NLS-1$
} catch (CoreException ce) {
// ignore
}
mTestRadioButton.setSelection(true);
setEnableSingleTestGroup(true);
setEnableContainerTestGroup(false);
mTestContainerRadioButton.setSelection(false);
mTestText.setText(testTypeName);
mContainerText.setText(EMPTY_STRING);
setTestMethodLabel(mOriginalTestMethodName);
}
private void setTestMethodLabel(String testMethodName) {
if (!EMPTY_STRING.equals(testMethodName)) {
mTestMethodLabel.setText(
JUnitMessages.JUnitLaunchConfigurationTab_label_method +
mOriginalTestMethodName);
} else {
mTestMethodLabel.setText(EMPTY_STRING);
}
}
private void updateTestContainerFromConfig(ILaunchConfiguration config) {
String containerHandle = EMPTY_STRING;
IJavaElement containerElement = null;
try {
containerHandle = config.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, EMPTY_STRING);
if (containerHandle.length() > 0) {
containerElement = JavaCore.create(containerHandle);
}
} catch (CoreException ce) {
// ignore
}
if (containerElement != null) {
mContainerElement = containerElement;
}
mTestContainerRadioButton.setSelection(true);
setEnableSingleTestGroup(false);
setEnableContainerTestGroup(true);
mTestRadioButton.setSelection(false);
if (mContainerElement != null) {
mContainerText.setText(getPresentationName(mContainerElement));
}
mTestText.setText(EMPTY_STRING);
}
/*
* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
public void performApply(ILaunchConfigurationWorkingCopy config) {
if (mTestContainerRadioButton.getSelection() && mContainerElement != null) {
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
mContainerElement.getJavaProject().getElementName());
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
mContainerElement.getHandleIdentifier());
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
EMPTY_STRING);
//workaround for Eclipse bug 65399
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
EMPTY_STRING);
} else {
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
mProjText.getText());
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
mTestText.getText());
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
EMPTY_STRING);
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_METHOD_NAME,
mOriginalTestMethodName);
}
try {
mapResources(config);
} catch (CoreException e) {
// TODO: does the real error need to be extracted out of CoreException
AdtPlugin.log(e, "Error occurred saving configuration"); //$NON-NLS-1$
}
AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
getSelectedInstrumentation());
}
private void mapResources(ILaunchConfigurationWorkingCopy config) throws CoreException {
JUnitMigrationDelegate.mapResources(config);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#dispose()
*/
@Override
public void dispose() {
super.dispose();
if (mTabIcon != null) {
mTabIcon.dispose();
mTabIcon = null;
}
mJavaElementLabelProvider.dispose();
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getImage()
*/
@Override
public Image getImage() {
// reuse icon from the Android App Launch config tab
if (mTabIcon == null) {
mTabIcon = AdtPlugin.getImageLoader().loadImage(MainLaunchConfigTab.LAUNCH_TAB_IMAGE,
null);
}
return mTabIcon;
}
/**
* Show a dialog that lists all main types
*/
private void handleSearchButtonSelected() {
Shell shell = getShell();
IJavaProject javaProject = getJavaProject();
IType[] types = new IType[0];
boolean[] radioSetting = new boolean[2];
try {
// fix for Eclipse bug 66922 Wrong radio behaviour when switching
// remember the selected radio button
radioSetting[0] = mTestRadioButton.getSelection();
radioSetting[1] = mTestContainerRadioButton.getSelection();
types = TestSearchEngine.findTests(getLaunchConfigurationDialog(), javaProject,
getTestKind());
} catch (InterruptedException e) {
setErrorMessage(e.getMessage());
return;
} catch (InvocationTargetException e) {
AdtPlugin.log(e.getTargetException(), "Error finding test types"); //$NON-NLS-1$
return;
} finally {
mTestRadioButton.setSelection(radioSetting[0]);
mTestContainerRadioButton.setSelection(radioSetting[1]);
}
SelectionDialog dialog = new TestSelectionDialog(shell, types);
dialog.setTitle(JUnitMessages.JUnitLaunchConfigurationTab_testdialog_title);
dialog.setMessage(JUnitMessages.JUnitLaunchConfigurationTab_testdialog_message);
if (dialog.open() == Window.CANCEL) {
return;
}
Object[] results = dialog.getResult();
if ((results == null) || (results.length < 1)) {
return;
}
IType type = (IType) results[0];
if (type != null) {
mTestText.setText(type.getFullyQualifiedName('.'));
javaProject = type.getJavaProject();
mProjText.setText(javaProject.getElementName());
}
}
private ITestKind getTestKind() {
// harddcode this to JUnit 3
return TestKindRegistry.getDefault().getKind(TestKindRegistry.JUNIT3_TEST_KIND_ID);
}
/**
* Show a dialog that lets the user select a Android project. This in turn provides
* context for the main type, allowing the user to key a main type name, or
* constraining the search for main types to the specified project.
*/
private void handleProjectButtonSelected() {
IJavaProject project = mProjectChooserHelper.chooseJavaProject(getProjectName());
if (project == null) {
return;
}
String projectName = project.getElementName();
mProjText.setText(projectName);
loadInstrumentations(project.getProject());
}
/**
* Return the IJavaProject corresponding to the project name in the project name
* text field, or null if the text does not match a Android project name.
*/
private IJavaProject getJavaProject() {
String projectName = getProjectName();
return getJavaModel().getJavaProject(projectName);
}
/**
* Returns the name of the currently specified project. Null if no project is selected.
*/
private String getProjectName() {
String projectName = mProjText.getText().trim();
if (projectName.length() < 1) {
return null;
}
return projectName;
}
/**
* Convenience method to get the workspace root.
*/
private IWorkspaceRoot getWorkspaceRoot() {
return ResourcesPlugin.getWorkspace().getRoot();
}
/**
* Convenience method to get access to the java model.
*/
private IJavaModel getJavaModel() {
return JavaCore.create(getWorkspaceRoot());
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#isValid(org.eclipse.debug.core.ILaunchConfiguration)
*/
@Override
public boolean isValid(ILaunchConfiguration config) {
validatePage();
return getErrorMessage() == null;
}
private void testModeChanged() {
boolean isSingleTestMode = mTestRadioButton.getSelection();
setEnableSingleTestGroup(isSingleTestMode);
setEnableContainerTestGroup(!isSingleTestMode);
if (!isSingleTestMode && mContainerText.getText().length() == 0) {
String projText = mProjText.getText();
if (Path.EMPTY.isValidSegment(projText)) {
IJavaProject javaProject = getJavaModel().getJavaProject(projText);
if (javaProject != null && javaProject.exists()) {
setContainerElement(javaProject);
}
}
}
validatePage();
updateLaunchConfigurationDialog();
}
private void validatePage() {
setErrorMessage(null);
setMessage(null);
if (mTestContainerRadioButton.getSelection()) {
if (mContainerElement == null) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_noContainer);
return;
}
validateJavaProject(mContainerElement.getJavaProject());
return;
}
String projectName = mProjText.getText().trim();
if (projectName.length() == 0) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_projectnotdefined);
return;
}
IStatus status = ResourcesPlugin.getWorkspace().validatePath(IPath.SEPARATOR + projectName,
IResource.PROJECT);
if (!status.isOK() || !Path.ROOT.isValidSegment(projectName)) {
setErrorMessage(Messages.format(
JUnitMessages.JUnitLaunchConfigurationTab_error_invalidProjectName,
projectName));
return;
}
IProject project = getWorkspaceRoot().getProject(projectName);
if (!project.exists()) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_projectnotexists);
return;
}
IJavaProject javaProject = JavaCore.create(project);
validateJavaProject(javaProject);
try {
if (!project.hasNature(AndroidConstants.NATURE)) {
setErrorMessage("Specified project is not an Android project");
return;
}
String className = mTestText.getText().trim();
if (className.length() == 0) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_testnotdefined);
return;
}
if (javaProject.findType(className) == null) {
setErrorMessage(Messages.format(
JUnitMessages.JUnitLaunchConfigurationTab_error_test_class_not_found,
new String[] { className, projectName }));
return;
}
} catch (CoreException e) {
AdtPlugin.log(e, "validatePage failed"); //$NON-NLS-1$
}
validateInstrumentation();
}
private void validateJavaProject(IJavaProject javaProject) {
if (!TestSearchEngine.hasTestCaseType(javaProject)) {
setErrorMessage(JUnitMessages.JUnitLaunchConfigurationTab_error_testcasenotonpath);
return;
}
}
private void validateInstrumentation() {
String instrumentation = getSelectedInstrumentation();
if (instrumentation == null) {
setErrorMessage("Instrumentation runner not specified");
return;
}
String result = mInstrValidator.validateInstrumentationRunner(instrumentation);
if (result != InstrumentationRunnerValidator.INSTRUMENTATION_OK) {
setErrorMessage(result);
return;
}
}
private String getSelectedInstrumentation() {
int selectionIndex = mInstrumentationCombo.getSelectionIndex();
if (mInstrumentations != null && selectionIndex >= 0 &&
selectionIndex < mInstrumentations.length) {
return mInstrumentations[selectionIndex];
}
return null;
}
private void setEnableContainerTestGroup(boolean enabled) {
mContainerSearchButton.setEnabled(enabled);
mContainerText.setEnabled(enabled);
}
private void setEnableSingleTestGroup(boolean enabled) {
mProjLabel.setEnabled(enabled);
mProjText.setEnabled(enabled);
mProjButton.setEnabled(enabled);
mTestLabel.setEnabled(enabled);
mTestText.setEnabled(enabled);
mSearchButton.setEnabled(enabled && mProjText.getText().length() > 0);
mTestMethodLabel.setEnabled(enabled);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
public void setDefaults(ILaunchConfigurationWorkingCopy config) {
IJavaElement javaElement = getContext();
if (javaElement != null) {
initializeJavaProject(javaElement, config);
} else {
// We set empty attributes for project & main type so that when one config is
// compared to another, the existence of empty attributes doesn't cause an
// incorrect result (the performApply() method can result in empty values
// for these attributes being set on a config if there is nothing in the
// corresponding text boxes)
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, EMPTY_STRING);
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
EMPTY_STRING);
}
initializeTestAttributes(javaElement, config);
}
private void initializeTestAttributes(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
if (javaElement != null && javaElement.getElementType() < IJavaElement.COMPILATION_UNIT) {
initializeTestContainer(javaElement, config);
} else {
initializeTestType(javaElement, config);
}
}
private void initializeTestContainer(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER,
javaElement.getHandleIdentifier());
initializeName(config, javaElement.getElementName());
}
private void initializeName(ILaunchConfigurationWorkingCopy config, String name) {
if (name == null) {
name = EMPTY_STRING;
}
if (name.length() > 0) {
int index = name.lastIndexOf('.');
if (index > 0) {
name = name.substring(index + 1);
}
name = getLaunchConfigurationDialog().generateName(name);
config.rename(name);
}
}
/**
* Sets the main type & name attributes on the working copy based on the IJavaElement
*/
private void initializeTestType(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
String name = EMPTY_STRING;
String testKindId = null;
try {
// only do a search for compilation units or class files or source references
if (javaElement instanceof ISourceReference) {
ITestKind testKind = TestKindRegistry.getContainerTestKind(javaElement);
testKindId = testKind.getId();
IType[] types = TestSearchEngine.findTests(getLaunchConfigurationDialog(),
javaElement, testKind);
if ((types == null) || (types.length < 1)) {
return;
}
// Simply grab the first main type found in the searched element
name = types[0].getFullyQualifiedName('.');
}
} catch (InterruptedException ie) {
// ignore
} catch (InvocationTargetException ite) {
// ignore
}
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, name);
if (testKindId != null) {
config.setAttribute(JUnitLaunchConfigurationConstants.ATTR_TEST_RUNNER_KIND,
testKindId);
}
initializeName(config, name);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName()
*/
public String getName() {
return JUnitMessages.JUnitLaunchConfigurationTab_tab_label;
}
@SuppressWarnings("unchecked")
private IJavaElement chooseContainer(IJavaElement initElement) {
Class[] acceptedClasses = new Class[] { IPackageFragmentRoot.class, IJavaProject.class,
IPackageFragment.class };
TypedElementSelectionValidator validator = new TypedElementSelectionValidator(
acceptedClasses, false) {
@Override
public boolean isSelectedValid(Object element) {
return true;
}
};
acceptedClasses = new Class[] { IJavaModel.class, IPackageFragmentRoot.class,
IJavaProject.class, IPackageFragment.class };
ViewerFilter filter = new TypedViewerFilter(acceptedClasses) {
@Override
public boolean select(Viewer viewer, Object parent, Object element) {
if (element instanceof IPackageFragmentRoot &&
((IPackageFragmentRoot) element).isArchive()) {
return false;
}
try {
if (element instanceof IPackageFragment &&
!((IPackageFragment) element).hasChildren()) {
return false;
}
} catch (JavaModelException e) {
return false;
}
return super.select(viewer, parent, element);
}
};
StandardJavaElementContentProvider provider = new StandardJavaElementContentProvider();
ILabelProvider labelProvider = new JavaElementLabelProvider(
JavaElementLabelProvider.SHOW_DEFAULT);
ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(),
labelProvider, provider);
dialog.setValidator(validator);
dialog.setComparator(new JavaElementComparator());
dialog.setTitle(JUnitMessages.JUnitLaunchConfigurationTab_folderdialog_title);
dialog.setMessage(JUnitMessages.JUnitLaunchConfigurationTab_folderdialog_message);
dialog.addFilter(filter);
dialog.setInput(JavaCore.create(getWorkspaceRoot()));
dialog.setInitialSelection(initElement);
dialog.setAllowMultiple(false);
if (dialog.open() == Window.OK) {
Object element = dialog.getFirstResult();
return (IJavaElement) element;
}
return null;
}
private String getPresentationName(IJavaElement element) {
return mJavaElementLabelProvider.getText(element);
}
/**
* Returns the current Java element context from which to initialize
* default settings, or <code>null</code> if none.
*
* @return Java element context.
*/
private IJavaElement getContext() {
IWorkbenchWindow activeWorkbenchWindow =
PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (activeWorkbenchWindow == null) {
return null;
}
IWorkbenchPage page = activeWorkbenchWindow.getActivePage();
if (page != null) {
ISelection selection = page.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection ss = (IStructuredSelection) selection;
if (!ss.isEmpty()) {
Object obj = ss.getFirstElement();
if (obj instanceof IJavaElement) {
return (IJavaElement) obj;
}
if (obj instanceof IResource) {
IJavaElement je = JavaCore.create((IResource) obj);
if (je == null) {
IProject pro = ((IResource) obj).getProject();
je = JavaCore.create(pro);
}
if (je != null) {
return je;
}
}
}
}
IEditorPart part = page.getActiveEditor();
if (part != null) {
IEditorInput input = part.getEditorInput();
return (IJavaElement) input.getAdapter(IJavaElement.class);
}
}
return null;
}
private void initializeJavaProject(IJavaElement javaElement,
ILaunchConfigurationWorkingCopy config) {
IJavaProject javaProject = javaElement.getJavaProject();
String name = null;
if (javaProject != null && javaProject.exists()) {
name = javaProject.getElementName();
}
config.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name);
}
private void setButtonGridData(Button button) {
GridData gridData = new GridData();
button.setLayoutData(gridData);
LayoutUtil.setButtonDimensionHint(button);
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.AbstractLaunchConfigurationTab#getId()
*/
@Override
public String getId() {
return "com.android.ide.eclipse.adt.launch.AndroidJUnitLaunchConfigurationTab"; //$NON-NLS-1$
}
/**
* Loads the UI with the instrumentations of the specified project, and stores the
* instrumentations in <code>mInstrumentations</code>.
*
* @param project the {@link IProject} to load the instrumentations from.
*/
private void loadInstrumentations(IProject project) {
try {
mInstrValidator = new InstrumentationRunnerValidator(project);
mInstrumentations = (mInstrValidator == null ? null :
mInstrValidator.getInstrumentations());
if (mInstrumentations != null) {
mInstrumentationCombo.removeAll();
for (String instrumentation : mInstrumentations) {
mInstrumentationCombo.add(instrumentation);
}
// the selection will be set when we update the ui from the current
// config object.
return;
}
} catch (CoreException e) {
AdtPlugin.logAndPrintError(e, TAG, "ERROR: Failed to get instrumentations for %1$s",
project.getName());
}
// if we reach this point, either project is null, or we got an exception during
// the parsing. In either case, we empty the instrumentation list.
mInstrValidator = null;
mInstrumentations = null;
mInstrumentationCombo.removeAll();
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.launch.junit;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.junit.launcher.JUnitLaunchShortcut;
/**
* Launch shortcut to launch debug/run Android JUnit configuration directly.
*/
public class AndroidJUnitLaunchShortcut extends JUnitLaunchShortcut {
@Override
protected String getLaunchConfigurationTypeId() {
return "com.android.ide.eclipse.adt.junit.launchConfigurationType"; //$NON-NLS-1$
}
/**
* Creates a default Android JUnit launch configuration. Sets the instrumentation runner to the
* first instrumentation found in the AndroidManifest.
*/
@Override
protected ILaunchConfigurationWorkingCopy createLaunchConfiguration(IJavaElement element)
throws CoreException {
ILaunchConfigurationWorkingCopy config = super.createLaunchConfiguration(element);
// just get first valid instrumentation runner
String instrumentation = new InstrumentationRunnerValidator(element.getJavaProject()).
getValidInstrumentationTestRunner();
if (instrumentation != null) {
config.setAttribute(AndroidJUnitLaunchConfigDelegate.ATTR_INSTR_NAME,
instrumentation);
}
// if a valid runner is not found, rely on launch delegate to log error.
// This method is called without explicit user action to launch Android JUnit, so avoid
// logging an error here.
AndroidJUnitLaunchConfigDelegate.setJUnitDefaults(config);
return config;
}
}

View File

@@ -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.launch.junit;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
import org.eclipse.debug.ui.CommonTab;
import org.eclipse.debug.ui.ILaunchConfigurationDialog;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
import com.android.ide.eclipse.adt.launch.EmulatorConfigTab;
/**
* Tab group object for Android JUnit launch configuration type.
*/
public class AndroidJUnitTabGroup extends AbstractLaunchConfigurationTabGroup {
/**
* Creates the UI tabs for the Android JUnit configuration
*/
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {
new AndroidJUnitLaunchConfigurationTab(),
new EmulatorConfigTab(),
new CommonTab()
};
setTabs(tabs);
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.launch.junit;
import com.android.ide.eclipse.adt.AdtPlugin;
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.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
/**
* Provides validation for Android instrumentation test runner
*/
class InstrumentationRunnerValidator {
private final IJavaProject mJavaProject;
private String[] mInstrumentations = null;
private boolean mHasRunnerLibrary = false;
static final String INSTRUMENTATION_OK = null;
/**
* Initializes the InstrumentationRunnerValidator.
*
* @param javaProject the {@link IJavaProject} for the Android project to validate
*/
InstrumentationRunnerValidator(IJavaProject javaProject) {
mJavaProject = javaProject;
try {
AndroidManifestParser manifestParser = AndroidManifestParser.parse(javaProject,
null /* errorListener */, true /* gatherData */, false /* markErrors */);
init(manifestParser);
} catch (CoreException e) {
AdtPlugin.printErrorToConsole(javaProject.getProject(), "ERROR: Failed to parse %1$s",
AndroidConstants.FN_ANDROID_MANIFEST);
}
}
/**
* Initializes the InstrumentationRunnerValidator.
*
* @param project the {@link IProject} for the Android project to validate
* @throws CoreException if a fatal error occurred in initialization
*/
InstrumentationRunnerValidator(IProject project) throws CoreException {
this(BaseProjectHelper.getJavaProject(project));
}
/**
* Initializes the InstrumentationRunnerValidator with an existing {@link AndroidManifestParser}
*
* @param javaProject the {@link IJavaProject} for the Android project to validate
* @param manifestParser the {@link AndroidManifestParser} for the Android project
*/
InstrumentationRunnerValidator(IJavaProject javaProject, AndroidManifestParser manifestParser) {
mJavaProject = javaProject;
init(manifestParser);
}
private void init(AndroidManifestParser manifestParser) {
mInstrumentations = manifestParser.getInstrumentations();
mHasRunnerLibrary = hasTestRunnerLibrary(manifestParser);
}
/**
* Helper method to determine if given manifest has a <code>AndroidConstants.LIBRARY_TEST_RUNNER
* </code> library reference
*
* @param manifestParser the {@link AndroidManifestParser} to search
* @return true if test runner library found, false otherwise
*/
private boolean hasTestRunnerLibrary(AndroidManifestParser manifestParser) {
for (String lib : manifestParser.getUsesLibraries()) {
if (lib.equals(AndroidConstants.LIBRARY_TEST_RUNNER)) {
return true;
}
}
return false;
}
/**
* Return the set of instrumentations for the Android project.
*
* @return <code>null</code if error occurred parsing instrumentations, otherwise returns array
* of instrumentation class names
*/
String[] getInstrumentations() {
return mInstrumentations;
}
/**
* Helper method to get the first instrumentation that can be used as a test runner.
*
* @return fully qualified instrumentation class name. <code>null</code> if no valid
* instrumentation can be found.
*/
String getValidInstrumentationTestRunner() {
for (String instrumentation : getInstrumentations()) {
if (validateInstrumentationRunner(instrumentation) == INSTRUMENTATION_OK) {
return instrumentation;
}
}
return null;
}
/**
* Helper method to determine if specified instrumentation can be used as a test runner
*
* @param instrumentation the instrumentation class name to validate. Assumes this
* instrumentation is one of {@link #getInstrumentations()}
* @return <code>INSTRUMENTATION_OK</code> if valid, otherwise returns error message
*/
String validateInstrumentationRunner(String instrumentation) {
if (!mHasRunnerLibrary) {
return String.format("The application does not declare uses-library %1$s",
AndroidConstants.LIBRARY_TEST_RUNNER);
}
// 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(mJavaProject,
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);
}
}
return INSTRUMENTATION_OK;
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.launch.junit.runtime;
import org.eclipse.core.resources.IProject;
import com.android.ddmlib.IDevice;
/**
* Contains info about Android JUnit launch
*/
public class AndroidJUnitLaunchInfo {
private final IProject mProject;
private final String mTestPackage;
private final String mRunner;
private final boolean mDebugMode;
private final IDevice mDevice;
public AndroidJUnitLaunchInfo(IProject project, String testPackage, String runner,
boolean debugMode, IDevice device) {
mProject = project;
mTestPackage = testPackage;
mRunner = runner;
mDebugMode = debugMode;
mDevice = device;
}
public IProject getProject() {
return mProject;
}
public String getTestPackage() {
return mTestPackage;
}
public String getRunner() {
return mRunner;
}
public boolean isDebugMode() {
return mDebugMode;
}
public IDevice getDevice() {
return mDevice;
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.launch.junit.runtime;
import org.eclipse.jdt.internal.junit.runner.ITestIdentifier;
import org.eclipse.jdt.internal.junit.runner.ITestReference;
import org.eclipse.jdt.internal.junit.runner.TestExecution;
/**
* Base implementation of the Eclipse {@link ITestReference} and {@link ITestIdentifier} interfaces
* for Android tests.
* <p/>
* Provides generic equality/hashcode services
*/
@SuppressWarnings("restriction") //$NON-NLS-1$
abstract class AndroidTestReference implements ITestReference, ITestIdentifier {
/**
* Gets the {@link ITestIdentifier} for this test reference.
*/
public ITestIdentifier getIdentifier() {
// this class serves as its own test identifier
return this;
}
/**
* Not supported.
*/
public void run(TestExecution execution) {
throw new UnsupportedOperationException();
}
/**
* Compares {@link ITestIdentifier} using names
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof ITestIdentifier) {
ITestIdentifier testid = (ITestIdentifier) obj;
return getName().equals(testid.getName());
}
return false;
}
@Override
public int hashCode() {
return getName().hashCode();
}
}

View File

@@ -0,0 +1,222 @@
/*
* 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.launch.junit.runtime;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.ide.eclipse.adt.AdtPlugin;
import org.eclipse.jdt.internal.junit.runner.MessageIds;
import org.eclipse.jdt.internal.junit.runner.RemoteTestRunner;
import org.eclipse.jdt.internal.junit.runner.TestExecution;
import org.eclipse.jdt.internal.junit.runner.TestReferenceFailure;
/**
* Supports Eclipse JUnit execution of Android tests.
* <p/>
* Communicates back to a Eclipse JDT JUnit client via a socket connection.
*
* @see org.eclipse.jdt.internal.junit.runner.RemoteTestRunner for more details on the protocol
*/
@SuppressWarnings("restriction")
public class RemoteAdtTestRunner extends RemoteTestRunner {
private AndroidJUnitLaunchInfo mLaunchInfo;
private TestExecution mExecution;
/**
* Initialize the JDT JUnit test runner parameters from the {@code args}.
*
* @param args name-value pair of arguments to pass to parent JUnit runner.
* @param launchInfo the Android specific test launch info
*/
protected void init(String[] args, AndroidJUnitLaunchInfo launchInfo) {
defaultInit(args);
mLaunchInfo = launchInfo;
}
/**
* Runs a set of tests, and reports back results using parent class.
* <p/>
* JDT Unit expects to be sent data in the following sequence:
* <ol>
* <li>The total number of tests to be executed.</li>
* <li>The test 'tree' data about the tests to be executed, which is composed of the set of
* test class names, the number of tests in each class, and the names of each test in the
* class.</li>
* <li>The test execution result for each test method. Expects individual notifications of
* the test execution start, any failures, and the end of the test execution.</li>
* <li>The end of the test run, with its elapsed time.</li>
* </ol>
* <p/>
* In order to satisfy this, this method performs two actual Android instrumentation runs.
* The first is a 'log only' run that will collect the test tree data, without actually
* executing the tests, and send it back to JDT JUnit. The second is the actual test execution,
* whose results will be communicated back in real-time to JDT JUnit.
*
* @param testClassNames array of fully qualified test class names to execute. Cannot be empty.
* @param testName test to execute. If null, will be ignored.
* @param execution used to report test progress
*/
@Override
public void runTests(String[] testClassNames, String testName, TestExecution execution) {
// hold onto this execution reference so it can be used to report test progress
mExecution = execution;
RemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(mLaunchInfo.getTestPackage(),
mLaunchInfo.getRunner(), mLaunchInfo.getDevice());
if (testClassNames != null && testClassNames.length > 0) {
if (testName != null) {
runner.setMethodName(testClassNames[0], testName);
} else {
runner.setClassNames(testClassNames);
}
}
// set log only to first collect test case info, so Eclipse has correct test case count/
// tree info
runner.setLogOnly(true);
TestCollector collector = new TestCollector();
runner.run(collector);
if (collector.getErrorMessage() != null) {
// error occurred during test collection.
reportError(collector.getErrorMessage());
// abort here
notifyTestRunEnded(0);
return;
}
notifyTestRunStarted(collector.getTestCaseCount());
collector.sendTrees(this);
// now do real execution
runner.setLogOnly(false);
if (mLaunchInfo.isDebugMode()) {
runner.setDebug(true);
}
runner.run(new TestRunListener());
}
/**
* Main entry method to run tests
*
* @param programArgs JDT JUnit program arguments to be processed by parent
* @param junitInfo the {@link AndroidJUnitLaunchInfo} containing info about this test ru
*/
public void runTests(String[] programArgs, AndroidJUnitLaunchInfo junitInfo) {
init(programArgs, junitInfo);
run();
}
/**
* Stop the current test run.
*/
public void terminate() {
stop();
}
@Override
protected void stop() {
if (mExecution != null) {
mExecution.stop();
}
}
private void notifyTestRunEnded(long elapsedTime) {
// copy from parent - not ideal, but method is private
sendMessage(MessageIds.TEST_RUN_END + elapsedTime);
flush();
//shutDown();
}
/**
* @param errorMessage
*/
private void reportError(String errorMessage) {
AdtPlugin.printErrorToConsole(mLaunchInfo.getProject(),
String.format("Test run failed: %s", errorMessage));
// is this needed?
//notifyTestRunStopped(-1);
}
/**
* TestRunListener that communicates results in real-time back to JDT JUnit
*/
private class TestRunListener implements ITestRunListener {
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testEnded(TestIdentifier test) {
mExecution.getListener().notifyTestEnded(new TestCaseReference(test));
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
*/
public void testFailed(TestFailure status, TestIdentifier test, String trace) {
String statusString;
if (status == TestFailure.ERROR) {
statusString = MessageIds.TEST_ERROR;
} else {
statusString = MessageIds.TEST_FAILED;
}
TestReferenceFailure failure =
new TestReferenceFailure(new TestCaseReference(test),
statusString, trace, null);
mExecution.getListener().notifyTestFailed(failure);
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
*/
public void testRunEnded(long elapsedTime) {
notifyTestRunEnded(elapsedTime);
AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run complete");
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
*/
public void testRunFailed(String errorMessage) {
reportError(errorMessage);
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
*/
public void testRunStarted(int testCount) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
*/
public void testRunStopped(long elapsedTime) {
notifyTestRunStopped(elapsedTime);
AdtPlugin.printToConsole(mLaunchInfo.getProject(), "Test run stopped");
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testStarted(TestIdentifier test) {
TestCaseReference testId = new TestCaseReference(test);
mExecution.getListener().notifyTestStarted(testId);
}
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.launch.junit.runtime;
import com.android.ddmlib.testrunner.TestIdentifier;
import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
import org.eclipse.jdt.internal.junit.runner.MessageIds;
import java.text.MessageFormat;
/**
* Reference for a single Android test method.
*/
@SuppressWarnings("restriction")
class TestCaseReference extends AndroidTestReference {
private final String mClassName;
private final String mTestName;
/**
* Creates a TestCaseReference from a class and method name
*/
TestCaseReference(String className, String testName) {
mClassName = className;
mTestName = testName;
}
/**
* Creates a TestCaseReference from a {@link TestIdentifier}
* @param test
*/
TestCaseReference(TestIdentifier test) {
mClassName = test.getClassName();
mTestName = test.getTestName();
}
/**
* Returns a count of the number of test cases referenced. Is always one for this class.
*/
public int countTestCases() {
return 1;
}
/**
* Sends test identifier and test count information for this test
*
* @param notified the {@link IVisitsTestTrees} to send test info to
*/
public void sendTree(IVisitsTestTrees notified) {
notified.visitTreeEntry(getIdentifier(), false, countTestCases());
}
/**
* Returns the identifier of this test, in a format expected by JDT JUnit
*/
public String getName() {
return MessageFormat.format(MessageIds.TEST_IDENTIFIER_MESSAGE_FORMAT,
new Object[] { mTestName, mClassName});
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.launch.junit.runtime;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.TestIdentifier;
import org.eclipse.jdt.internal.junit.runner.ITestReference;
import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
import java.util.HashMap;
import java.util.Map;
/**
* Collects info about tests to be executed by listening to the results of an Android test run.
*/
@SuppressWarnings("restriction")
class TestCollector implements ITestRunListener {
private int mTotalTestCount;
/** test name to test suite reference map. */
private Map<String, TestSuiteReference> mTestTree;
private String mErrorMessage = null;
TestCollector() {
mTotalTestCount = 0;
mTestTree = new HashMap<String, TestSuiteReference>();
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testEnded(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testEnded(TestIdentifier test) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testFailed(com.android.ddmlib.testrunner.ITestRunListener.TestFailure, com.android.ddmlib.testrunner.TestIdentifier, java.lang.String)
*/
public void testFailed(TestFailure status, TestIdentifier test, String trace) {
// ignore - should be impossible since this is only collecting test information
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunEnded(long)
*/
public void testRunEnded(long elapsedTime) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunFailed(java.lang.String)
*/
public void testRunFailed(String errorMessage) {
mErrorMessage = errorMessage;
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStarted(int)
*/
public void testRunStarted(int testCount) {
mTotalTestCount = testCount;
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testRunStopped(long)
*/
public void testRunStopped(long elapsedTime) {
// ignore
}
/* (non-Javadoc)
* @see com.android.ddmlib.testrunner.ITestRunListener#testStarted(com.android.ddmlib.testrunner.TestIdentifier)
*/
public void testStarted(TestIdentifier test) {
TestSuiteReference suiteRef = mTestTree.get(test.getClassName());
if (suiteRef == null) {
// this test suite has not been seen before, create it
suiteRef = new TestSuiteReference(test.getClassName());
mTestTree.put(test.getClassName(), suiteRef);
}
suiteRef.addTest(new TestCaseReference(test));
}
/**
* Returns the total test count in the test run.
*/
public int getTestCaseCount() {
return mTotalTestCount;
}
/**
* Sends info about the test tree to be executed (ie the suites and their enclosed tests)
*
* @param notified the {@link IVisitsTestTrees} to send test data to
*/
public void sendTrees(IVisitsTestTrees notified) {
for (ITestReference ref : mTestTree.values()) {
ref.sendTree(notified);
}
}
/**
* Returns the error message that was reported when collecting test info.
* Returns <code>null</code> if no error occurred.
*/
public String getErrorMessage() {
return mErrorMessage;
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.launch.junit.runtime;
import org.eclipse.jdt.internal.junit.runner.IVisitsTestTrees;
import java.util.List;
import java.util.ArrayList;
/**
* Reference for an Android test suite aka class.
*/
@SuppressWarnings("restriction")
class TestSuiteReference extends AndroidTestReference {
private final String mClassName;
private List<TestCaseReference> mTests;
/**
* Creates a TestSuiteReference
*
* @param className the fully qualified name of the test class
*/
TestSuiteReference(String className) {
mClassName = className;
mTests = new ArrayList<TestCaseReference>();
}
/**
* Returns a count of the number of test cases included in this suite.
*/
public int countTestCases() {
return mTests.size();
}
/**
* Sends test identifier and test count information for this test class, and all its included
* test methods.
*
* @param notified the {@link IVisitsTestTrees} to send test info too
*/
public void sendTree(IVisitsTestTrees notified) {
notified.visitTreeEntry(getIdentifier(), true, countTestCases());
for (TestCaseReference ref : mTests) {
ref.sendTree(notified);
}
}
/**
* Return the name of this test class.
*/
public String getName() {
return mClassName;
}
/**
* Adds a test method to this suite.
*
* @param testRef the {@link TestCaseReference} to add
*/
void addTest(TestCaseReference testRef) {
mTests.add(testRef);
}
}

View File

@@ -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.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser; 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.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription; 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.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.JavaRuntime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* Utility class to manipulate Project parameters/properties. * Utility class to manipulate Project parameters/properties.
@@ -679,4 +683,71 @@ public final class ProjectHelper {
return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; 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<IJavaProject> 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<IJavaProject> projectList = new ArrayList<IJavaProject>(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;
}
} }

View File

@@ -487,6 +487,15 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
IJavaProject javaProject = projects.get(i); IJavaProject javaProject = projects.get(i);
IProject iProject = javaProject.getProject(); IProject iProject = javaProject.getProject();
// check if the project is opened
if (iProject.isOpen() == false) {
// remove from the list
// we do not increment i in this case.
projects.remove(i);
continue;
}
// get the target from the project and its paths // get the target from the project and its paths
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject()); IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
if (target == null) { if (target == null) {

View File

@@ -0,0 +1,170 @@
/*
* 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 org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
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;
import org.eclipse.ui.part.FileEditorInput;
/*
* 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.
* <p/>
* The intent of the action is to start a refactoring that extracts a source string and
* replaces it by an Android string resource ID.
* <p/>
* Workflow:
* <ul>
* <li> The action is currently located in the Refactoring menu in the main menu.
* <li> TODO: extend the popup refactoring menu in a Java or Android XML file.
* <li> 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.
* <ul> 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.
* <ul> 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.
* <li> The action creates a new {@link ExtractStringRefactoring} and make it run on in a new
* {@link ExtractStringWizard}.
* <ul>
*/
public class ExtractStringAction implements IWorkbenchWindowActionDelegate {
/** Keep track of the current workbench window. */
private IWorkbenchWindow mWindow;
private ITextSelection mSelection;
private IFile mFile;
/**
* 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.
* <p/>
* 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;
mFile = null;
if (selection instanceof ITextSelection) {
mSelection = (ITextSelection) selection;
if (mSelection.getLength() > 0) {
mFile = getSelectedFile();
}
}
action.setEnabled(mSelection != null && mFile != null);
}
/**
* Create a new instance of our refactoring and a wizard to configure it.
*/
public void run(IAction action) {
if (mSelection != null && mFile != null) {
ExtractStringRefactoring ref = new ExtractStringRefactoring(mFile, mSelection);
RefactoringWizard wizard = new ExtractStringWizard(ref, mFile.getProject());
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
try {
op.run(mWindow.getShell(), wizard.getDefaultPageTitle());
} catch (InterruptedException e) {
// Interrupted. Pass.
}
}
}
/**
* Returns the active {@link IFile} (hopefully matching our selection) or null.
* The file is only returned if it's a file from a project with an Android nature.
* <p/>
* At that point we do not try to analyze if the selection nor the file is suitable
* for the refactoring. This check is performed when the refactoring is invoked since
* it can then produce meaningful error messages as needed.
*/
private IFile getSelectedFile() {
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 instanceof FileEditorInput) {
FileEditorInput fi = (FileEditorInput) input;
IFile file = fi.getFile();
if (file.exists()) {
IProject proj = file.getProject();
try {
if (proj != null && proj.hasNature(AndroidConstants.NATURE)) {
return file;
}
} catch (CoreException e) {
// ignore
}
}
}
}
}
}
return null;
}
}

View File

@@ -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);
}
}

View File

@@ -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<String, String> mArguments;
public ExtractStringDescriptor(String project, String description, String comment,
Map<String, String> arguments) {
super(ID, project, description, comment,
RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE //flags
);
mArguments = arguments;
}
public Map<String, String> 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)}
* <p/>
* 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;
}
}
}

View File

@@ -0,0 +1,477 @@
/*
* 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.editors.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.editors.wizards.ConfigurationSelector;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardPage;
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.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @see ExtractStringRefactoring
*/
class ExtractStringInputPage extends UserInputWizardPage implements IWizardPage {
/** Last res file path used, shared across the session instances but specific to the
* current project. The default for unknown projects is {@link #DEFAULT_RES_FILE_PATH}. */
private static HashMap<String, String> sLastResFilePath = new HashMap<String, String>();
/** The project where the user selection happened. */
private final IProject mProject;
/** Field displaying the user-selected string to be replaced. */
private Label mStringLabel;
/** Test field where the user enters the new ID to be generated or replaced with. */
private Text mNewIdTextField;
/** The configuration selector, to select the resource path of the XML file. */
private ConfigurationSelector mConfigSelector;
/** The combo to display the existing XML files or enter a new one. */
private Combo mResFileCombo;
/** Regex pattern to read a valid res XML file path. It checks that the are 2 folders and
* a leaf file name ending with .xml */
private static final Pattern RES_XML_FILE_REGEX = Pattern.compile(
"/res/[a-z][a-zA-Z0-9_-]+/[^.]+\\.xml"); //$NON-NLS-1$
/** Absolute destination folder root, e.g. "/res/" */
private static final String RES_FOLDER_ABS =
AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
/** Relative destination folder root, e.g. "res/" */
private static final String RES_FOLDER_REL =
SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
private static final String DEFAULT_RES_FILE_PATH = "/res/values/strings.xml";
public ExtractStringInputPage(IProject project) {
super("ExtractStringInputPage"); //$NON-NLS-1$
mProject = project;
}
/**
* Create the UI for the refactoring wizard.
* <p/>
* Note that at that point the initial conditions have been checked in
* {@link ExtractStringRefactoring}.
*/
public void createControl(Composite parent) {
Composite content = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 1;
content.setLayout(layout);
createStringReplacementGroup(content);
createResFileGroup(content);
validatePage();
setControl(content);
}
/**
* Creates the top group with the field to replace which string and by what
* and by which options.
*
* @param content A composite with a 1-column grid layout
*/
private void createStringReplacementGroup(Composite content) {
final ExtractStringRefactoring ref = getOurRefactoring();
Group group = new Group(content, SWT.NONE);
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
group.setText("String Replacement");
GridLayout layout = new GridLayout();
layout.numColumns = 2;
group.setLayout(layout);
// line: String found in selection
Label label = new Label(group, SWT.NONE);
label.setText("String:");
String selectedString = ref.getTokenString();
mStringLabel = new Label(group, 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 : Textfield for new ID
label = new Label(group, SWT.NONE);
label.setText("Replace by R.string.");
mNewIdTextField = new Text(group, 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.setReplacementStringId(mNewIdTextField.getText().trim());
}
}
});
}
/**
* Creates the lower group with the fields to choose the resource confirmation and
* the target XML file.
*
* @param content A composite with a 1-column grid layout
*/
private void createResFileGroup(Composite content) {
Group group = new Group(content, SWT.NONE);
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
group.setText("XML resource to edit");
GridLayout layout = new GridLayout();
layout.numColumns = 2;
group.setLayout(layout);
// line: selection of the res config
Label label;
label = new Label(group, SWT.NONE);
label.setText("Configuration:");
mConfigSelector = new ConfigurationSelector(group);
GridData gd = new GridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL);
gd.widthHint = ConfigurationSelector.WIDTH_HINT;
gd.heightHint = ConfigurationSelector.HEIGHT_HINT;
mConfigSelector.setLayoutData(gd);
OnConfigSelectorUpdated onConfigSelectorUpdated = new OnConfigSelectorUpdated();
mConfigSelector.setOnChangeListener(onConfigSelectorUpdated);
// line: selection of the output file
label = new Label(group, SWT.NONE);
label.setText("Resource file:");
mResFileCombo = new Combo(group, SWT.DROP_DOWN);
mResFileCombo.select(0);
mResFileCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mResFileCombo.addModifyListener(onConfigSelectorUpdated);
// set output file name to the last one used
String projPath = mProject.getFullPath().toPortableString();
String filePath = sLastResFilePath.get(projPath);
mResFileCombo.setText(filePath != null ? filePath : DEFAULT_RES_FILE_PATH);
onConfigSelectorUpdated.run();
}
/**
* Utility method to guess a suitable new XML ID based on the selected string.
*/
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;
}
/**
* Returns the {@link ExtractStringRefactoring} instance used by this wizard page.
*/
private ExtractStringRefactoring getOurRefactoring() {
return (ExtractStringRefactoring) getRefactoring();
}
/**
* Validates fields of the wizard input page. Displays errors as appropriate and
* enable the "Next" button (or not) by calling {@link #setPageComplete(boolean)}.
*
* @return True if the page has been positively validated. It may still have warnings.
*/
private boolean validatePage() {
boolean success = true;
// Analyze fatal errors.
String text = mNewIdTextField.getText().trim();
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;
}
}
}
String resFile = mResFileCombo.getText();
if (success) {
if (resFile == null || resFile.length() == 0) {
setErrorMessage("A resource file name is required.");
success = false;
} else if (!RES_XML_FILE_REGEX.matcher(resFile).matches()) {
setErrorMessage("The XML file name is not valid.");
success = false;
}
}
// Analyze info & warnings.
if (success) {
setErrorMessage(null);
ExtractStringRefactoring ref = getOurRefactoring();
ref.setTargetFile(resFile);
sLastResFilePath.put(mProject.getFullPath().toPortableString(), resFile);
if (ref.isResIdDuplicate(resFile, text)) {
setMessage(
String.format("There's already a string item called '%1$s' in %2$s.",
text, resFile),
WizardPage.WARNING);
} else if (mProject.findMember(resFile) == null) {
setMessage(
String.format("File %2$s does not exist and will be created.",
text, resFile),
WizardPage.INFORMATION);
} else {
setMessage(null);
}
}
setPageComplete(success);
return success;
}
public class OnConfigSelectorUpdated implements Runnable, ModifyListener {
/** Regex pattern to parse a valid res path: it reads (/res/folder-name/)+(filename). */
private final Pattern mPathRegex = Pattern.compile(
"(/res/[a-z][a-zA-Z0-9_-]+/)(.+)"); //$NON-NLS-1$
/** Temporary config object used to retrieve the Config Selector value. */
private FolderConfiguration mTempConfig = new FolderConfiguration();
private HashMap<String, TreeSet<String>> mFolderCache =
new HashMap<String, TreeSet<String>>();
private String mLastFolderUsedInCombo = null;
private boolean mInternalConfigChange;
private boolean mInternalFileComboChange;
/**
* Callback invoked when the {@link ConfigurationSelector} has been changed.
* <p/>
* The callback does the following:
* <ul>
* <li> Examine the current file name to retrieve the XML filename, if any.
* <li> Recompute the path based on the configuration selector (e.g. /res/values-fr/).
* <li> Examine the path to retrieve all the files in it. Keep those in a local cache.
* <li> If the XML filename from step 1 is not in the file list, it's a custom file name.
* Insert it and sort it.
* <li> Re-populate the file combo with all the choices.
* <li> Select the original XML file.
*/
public void run() {
if (mInternalConfigChange) {
return;
}
// get current leafname, if any
String leafName = "";
String currPath = mResFileCombo.getText();
Matcher m = mPathRegex.matcher(currPath);
if (m.matches()) {
// Note: groups 1 and 2 cannot be null.
leafName = m.group(2);
currPath = m.group(1);
} else {
// There was a path but it was invalid. Ignore it.
currPath = "";
}
// recreate the res path from the current configuration
mConfigSelector.getConfiguration(mTempConfig);
StringBuffer sb = new StringBuffer(RES_FOLDER_ABS);
sb.append(mTempConfig.getFolderName(ResourceFolderType.VALUES));
sb.append('/');
String newPath = sb.toString();
if (newPath.equals(currPath) && newPath.equals(mLastFolderUsedInCombo)) {
// Path has not changed. No need to reload.
return;
}
// Get all the files at the new path
TreeSet<String> filePaths = mFolderCache.get(newPath);
if (filePaths == null) {
filePaths = new TreeSet<String>();
IFolder folder = mProject.getFolder(newPath);
if (folder != null && folder.exists()) {
try {
for (IResource res : folder.members()) {
String name = res.getName();
if (res.getType() == IResource.FILE && name.endsWith(".xml")) {
filePaths.add(newPath + name);
}
}
} catch (CoreException e) {
// Ignore.
}
}
mFolderCache.put(newPath, filePaths);
}
currPath = newPath + leafName;
if (leafName.length() > 0 && !filePaths.contains(currPath)) {
filePaths.add(currPath);
}
// Fill the combo
try {
mInternalFileComboChange = true;
mResFileCombo.removeAll();
for (String filePath : filePaths) {
mResFileCombo.add(filePath);
}
int index = -1;
if (leafName.length() > 0) {
index = mResFileCombo.indexOf(currPath);
if (index >= 0) {
mResFileCombo.select(index);
}
}
if (index == -1) {
mResFileCombo.setText(currPath);
}
mLastFolderUsedInCombo = newPath;
} finally {
mInternalFileComboChange = false;
}
// finally validate the whole page
validatePage();
}
/**
* Callback invoked when {@link ExtractStringInputPage#mResFileCombo} has been
* modified.
*/
public void modifyText(ModifyEvent e) {
if (mInternalFileComboChange) {
return;
}
String wsFolderPath = mResFileCombo.getText();
// This is a custom path, we need to sanitize it.
// First it should start with "/res/". Then we need to make sure there are no
// relative paths, things like "../" or "./" or even "//".
wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$
wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$
wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
// We get "res/foo" from selections relative to the project when we want a "/res/foo" path.
if (wsFolderPath.startsWith(RES_FOLDER_REL)) {
wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length());
mInternalFileComboChange = true;
mResFileCombo.setText(wsFolderPath);
mInternalFileComboChange = false;
}
if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length());
int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR);
if (pos >= 0) {
wsFolderPath = wsFolderPath.substring(0, pos);
}
String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP);
if (folderSegments.length > 0) {
String folderName = folderSegments[0];
if (folderName != null && !folderName.equals(wsFolderPath)) {
// update config selector
mInternalConfigChange = true;
mConfigSelector.setConfiguration(folderSegments);
mInternalConfigChange = false;
}
}
}
validatePage();
}
}
}

View File

@@ -0,0 +1,965 @@
/*
* 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.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.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.TextEditChangeGroup;
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.
* <p/>
* There are a number of scenarios, which are not all supported yet. The workflow works as
* such:
* <ul>
* <li> User selects a string in a Java (TODO: or XML file) and invokes
* the {@link ExtractStringAction}.
* <li> 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.
* <li> 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. <string>foo</string>) for values, etc.
* <li> 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.
* <li> TODO: Find the string in an XML file based on selection.
* <li> On success, the wizard is shown, which let the user input the new ID to use.
* <li> 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.
* <li> 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.
* <li> When all changes are computed, {@link #createChange(IProgressMonitor)} is invoked.
* </ul>
*
* The list of changes are:
* <ul>
* <li> If the target XML does not exist, create it with the new string ID.
* <li> If the target XML exists, find the <resources> node and add the new string ID right after.
* If the node is <resources/>, it needs to be opened.
* <li> 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.
* <li> TODO: If the source is an XML file, determine if we need to change an attribute or a
* a text element.
* <li> TODO: Have a pref in the wizard: [x] Change other XML Files
* <li> TODO: Have a pref in the wizard: [x] Change other Java Files
* </ul>
*/
class ExtractStringRefactoring extends Refactoring {
/** The file model being manipulated. */
private final IFile mFile;
/** The start of the selection in {@link #mFile}. */
private final int mSelectionStart;
/** The end of the selection in {@link #mFile}. */
private final int mSelectionEnd;
/** The compilation unit, only defined if {@link #mFile} points to a usable Java source file. */
private ICompilationUnit mUnit;
/** The actual string selected, after UTF characters have been escaped, good for display. */
private String mTokenString;
/** The XML string ID selected by the user in the wizard. */
private String mXmlStringId;
/** The path of the XML file that will define {@link #mXmlStringId}, selected by the user
* in the wizard. */
private String mTargetXmlFileWsPath;
/** A temporary cache of R.string IDs defined by a given xml file. The key is the
* project path of the file, the data is a set of known string Ids for that file. */
private HashMap<String,HashSet<String>> mResIdCache;
/** An instance of XPath, created lazily on demand. */
private XPath mXPath;
/** The list of changes computed by {@link #checkFinalConditions(IProgressMonitor)} and
* used by {@link #createChange(IProgressMonitor)}. */
private ArrayList<Change> mChanges;
public ExtractStringRefactoring(Map<String, String> arguments)
throws NullPointerException {
IPath path = Path.fromPortableString(arguments.get("file")); //$NON-NLS-1$
mFile = (IFile) ResourcesPlugin.getWorkspace().getRoot().findMember(path);
mSelectionStart = Integer.parseInt(arguments.get("sel-start")); //$NON-NLS-1$
mSelectionEnd = Integer.parseInt(arguments.get("sel-end")); //$NON-NLS-1$
mTokenString = arguments.get("tok-esc"); //$NON-NLS-1$
}
private Map<String, String> createArgumentMap() {
HashMap<String, String> args = new HashMap<String, String>();
args.put("file", mFile.getFullPath().toPortableString()); //$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-esc", mTokenString); //$NON-NLS-1$
return args;
}
public ExtractStringRefactoring(IFile file, ITextSelection selection) {
mFile = file;
mSelectionStart = selection.getOffset();
mSelectionEnd = mSelectionStart + Math.max(0, selection.getLength() - 1);
}
/**
* @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.
* <p/>
* 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.
* <p/>
* 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 {
mUnit = null;
mTokenString = null;
RefactoringStatus status = new RefactoringStatus();
try {
monitor.beginTask("Checking preconditions...", 5);
if (!checkSourceFile(mFile, status, monitor)) {
return status;
}
// Try to get a compilation unit from this file. If it fails, mUnit is null.
try {
mUnit = JavaCore.createCompilationUnitFrom(mFile);
// Make sure the unit is not read-only, e.g. it's not a class file or inside a Jar
if (mUnit.isReadOnly()) {
status.addFatalError("The file is read-only, please make it writeable first.");
return status;
}
// This is a Java file. Check if it contains the selection we want.
if (!findSelectionInJavaUnit(mUnit, status, monitor)) {
return status;
}
} catch (Exception e) {
// That was not a Java file. Ignore.
}
if (mUnit == null) {
// Check this an XML file and get the selection and its context.
// TODO
status.addFatalError("Selection must be inside a Java source file.");
}
} finally {
monitor.done();
}
return status;
}
/**
* Try to find the selected Java element in the compilation unit.
*
* If selection matches a string literal, capture it, otherwise add a fatal error
* to the status.
*
* On success, advance the monitor by 3.
*/
private boolean findSelectionInJavaUnit(ICompilationUnit unit,
RefactoringStatus status, IProgressMonitor monitor) {
try {
IBuffer buffer = unit.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());
}
break;
} else if (scanner.getCurrentTokenStartPosition() > mSelectionEnd) {
// scanner is past the selection, abort.
break;
}
}
} catch (JavaModelException e1) {
// Error in unit.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);
return status.isOK();
}
/**
* Tests from org.eclipse.jdt.internal.corext.refactoringChecks#validateEdit()
* Might not be useful.
*
* On success, advance the monitor by 2.
*
* @return False if caller should abort, true if caller should continue.
*/
private boolean checkSourceFile(IFile file,
RefactoringStatus status,
IProgressMonitor monitor) {
// check whether the source file is in sync
if (!file.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 = file.getResourceAttributes();
if (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.
* <p/>
* 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<Change>();
// Prepare the change for the XML file.
if (!isResIdDuplicate(mTargetXmlFileWsPath, mXmlStringId)) {
// We actually change it only if the ID doesn't exist yet
Change change = createXmlChange((IFile) targetXml, mXmlStringId, mTokenString,
status, SubMonitor.convert(monitor, 1));
if (change != null) {
mChanges.add(change);
}
}
if (status.hasError()) {
return status;
}
// Prepare the change to the Java compilation unit
List<Change> 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 Change} that adds the given
* ID to the given XML File.
* <p/>
* This does not actually modify the file.
*
* @param targetXml The file resource to modify.
* @param xmlStringId The new ID to insert.
* @param tokenString 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 Change createXmlChange(IFile targetXml,
String xmlStringId,
String tokenString,
RefactoringStatus status,
SubMonitor subMonitor) {
TextFileChange xmlChange = new TextFileChange(getName(), targetXml);
xmlChange.setTextType("xml"); //$NON-NLS-1$
TextEdit edit = null;
TextEditGroup editGroup = null;
if (!targetXml.exists()) {
// The XML file does not exist. Simply create it.
StringBuilder content = new StringBuilder();
content.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$
content.append("<resources>\n"); //$NON-NLS-1$
content.append(" <string name=\""). //$NON-NLS-1$
append(xmlStringId).
append("\">"). //$NON-NLS-1$
append(tokenString).
append("</string>\n"); //$NON-NLS-1$
content.append("<resources>\n"); //$NON-NLS-1$
edit = new InsertEdit(0, content.toString());
editGroup = new TextEditGroup("Create ID in new XML file", edit);
} else {
// The file exist. Attempt to parse it as a valid XML document.
try {
int[] indices = new int[2];
if (findXmlOpeningTagPos(targetXml.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(" <string name=\""). //$NON-NLS-1$
append(xmlStringId).
append("\">"). //$NON-NLS-1$
append(tokenString).
append("</string>"); //$NON-NLS-1$
if (len == 2) {
content.append("\n</resources>"); //$NON-NLS-1$
}
edit = new ReplaceEdit(offset, len, content.toString());
editGroup = new TextEditGroup("Insert ID in XML file", edit);
}
} catch (CoreException e) {
// Failed to read file. Ignore. Will return null below.
}
}
if (edit == null) {
status.addFatalError(String.format("Failed to modify file %1$s",
mTargetXmlFileWsPath));
return null;
}
xmlChange.setEdit(edit);
// The TextEditChangeGroup let the user toggle this change on and off later.
xmlChange.addTextEditChangeGroup(new TextEditChangeGroup(xmlChange, editGroup));
subMonitor.worked(1);
return xmlChange;
}
/**
* Parse an XML input stream, looking for an opening tag.
* <p/>
* If found, returns the character offest in the buffer of the closing bracket of that
* tag, e.g. the position of > in "<resources>". The first character is at offset 0.
* <p/>
* 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.
* <p/>
* We need to deal with the case where the element is written as <resources/>, 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 <code>indices</code> 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;
}
/**
* Computes the changes to be made to Java file(s) and returns a list of {@link Change}.
*/
private List<Change> 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<Change> changes = new ArrayList<Change>();
// 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 importRewrite = ImportRewrite.create((CompilationUnit) node, true);
String Rqualifier = packageName + ".R"; //$NON-NLS-1$
Rqualifier = importRewrite.addImport(Rqualifier);
// Rewrite the AST itself via an ASTVisitor
AST ast = node.getAST();
ASTRewrite astRewrite = ASTRewrite.create(ast);
ArrayList<TextEditGroup> astEditGroups = new ArrayList<TextEditGroup>();
ReplaceStringsVisitor visitor = new ReplaceStringsVisitor(
ast, astRewrite, astEditGroups,
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 = importRewrite.rewriteImports(subMonitor.newChild(1));
if (subEdit.hasChildren()) {
edit.addChild(subEdit);
}
// Create the edit to change the Java source, only if anything changed
subEdit = astRewrite.rewriteAST();
if (subEdit.hasChildren()) {
edit.addChild(subEdit);
}
// Only create a change set if any edit was collected
if (edit.hasChildren()) {
change.setEdit(edit);
// Create TextEditChangeGroups which let the user turn changes on or off
// individually. This must be done after the change.setEdit() call above.
for (TextEditGroup editGroup : astEditGroups) {
change.addTextEditChangeGroup(new TextEditChangeGroup(change, editGroup));
}
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;
}
subMonitor.worked(1);
} 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;
private final ArrayList<TextEditGroup> mEditGroups;
public ReplaceStringsVisitor(AST ast,
ASTRewrite astRewrite,
ArrayList<TextEditGroup> editGroups,
String oldString,
String rQualifier,
String xmlId) {
mAst = ast;
mRewriter = astRewrite;
mEditGroups = editGroups;
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("Replace string by ID");
mEditGroups.add(editGroup);
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<String, HashSet<String>>();
}
HashSet<String> 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<String> getResIdsForFile(String xmlFileWsPath) {
HashSet<String> ids = new HashSet<String>();
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:
// <resources>
// <string name="ID">something</string>
// </resources>
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 = mFile.getProject();
IResource resource = proj.getFile(xmlFileWsPath);
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;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.IProject;
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 {
private final IProject mProject;
/**
* Create a wizard for ExtractString based on a simple dialog with one page.
*
* @param ref The instance of {@link ExtractStringRefactoring} to associate to the wizard.
* @param project The project where the wizard was invoked from (e.g. where the user selection
* happened, so that we can retrieve project resources.)
*/
public ExtractStringWizard(ExtractStringRefactoring ref, IProject project) {
super(ref, DIALOG_BASED_USER_INTERFACE | PREVIEW_EXPAND_FIRST_NODE);
mProject = project;
setDefaultPageTitle(ref.getName());
}
@Override
protected void addUserInputPages() {
addPage(new ExtractStringInputPage(mProject));
}
}

View File

@@ -157,7 +157,7 @@ public class LayoutParamsParser {
superClasses[2] = paramsClassName; superClasses[2] = paramsClassName;
} }
HashMap<String, ArrayList<IClassDescriptor>> found = HashMap<String, ArrayList<IClassDescriptor>> found =
mClassLoader.findClassesDerivingFrom("android.", superClasses); mClassLoader.findClassesDerivingFrom("android.", superClasses); //$NON-NLS-1$
mTopViewClass = mClassLoader.getClass(rootClassName); mTopViewClass = mClassLoader.getClass(rootClassName);
mTopGroupClass = mClassLoader.getClass(groupClassName); mTopGroupClass = mClassLoader.getClass(groupClassName);
if (paramsClassName != null) { if (paramsClassName != null) {
@@ -179,8 +179,7 @@ public class LayoutParamsParser {
addView(mTopViewClass); addView(mTopViewClass);
// ViewGroup derives from View // ViewGroup derives from View
mGroupMap.get(groupClassName).setSuperClass( mGroupMap.get(groupClassName).setSuperClass(mViewMap.get(rootClassName));
mViewMap.get(rootClassName));
progress.setWorkRemaining(mGroupList.size() + mViewList.size()); progress.setWorkRemaining(mGroupList.size() + mViewList.size());
@@ -346,7 +345,7 @@ public class LayoutParamsParser {
private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) { private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) {
IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses(); IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses();
for (IClassDescriptor innerClass : innerClasses) { for (IClassDescriptor innerClass : innerClasses) {
if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) { if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_NAME_LAYOUTPARAMS)) {
return innerClass; return innerClass;
} }
} }

View File

@@ -22,7 +22,9 @@
package com.android.ide.eclipse.adt.wizards.newproject; package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
@@ -122,6 +124,7 @@ public class NewProjectCreationPage extends WizardPage {
private Button mCreateActivityCheck; private Button mCreateActivityCheck;
private Text mMinSdkVersionField; private Text mMinSdkVersionField;
private SdkTargetSelector mSdkTargetSelector; private SdkTargetSelector mSdkTargetSelector;
private ITargetChangeListener mSdkTargetChangeListener;
private boolean mInternalLocationPathUpdate; private boolean mInternalLocationPathUpdate;
protected boolean mInternalProjectNameUpdate; protected boolean mInternalProjectNameUpdate;
@@ -264,6 +267,17 @@ public class NewProjectCreationPage extends WizardPage {
setPageComplete(validatePage()); setPageComplete(validatePage());
} }
@Override
public void dispose() {
if (mSdkTargetChangeListener != null) {
AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
mSdkTargetChangeListener = null;
}
super.dispose();
}
/** /**
* Creates the group for the project name: * Creates the group for the project name:
* [label: "Project Name"] [text field] * [label: "Project Name"] [text field]
@@ -389,18 +403,35 @@ public class NewProjectCreationPage extends WizardPage {
group.setFont(parent.getFont()); group.setFont(parent.getFont());
group.setText("Target"); group.setText("Target");
// The selector is created without targets. They are added below in the change listener.
mSdkTargetSelector = new SdkTargetSelector(group, null, false /*multi-selection*/);
mSdkTargetChangeListener = new ITargetChangeListener() {
public void onProjectTargetChange(IProject changedProject) {
// Ignore
}
public void onTargetsLoaded() {
// Update the sdk target selector with the new targets
// get the targets from the sdk // get the targets from the sdk
IAndroidTarget[] targets = null; IAndroidTarget[] targets = null;
if (Sdk.getCurrent() != null) { if (Sdk.getCurrent() != null) {
targets = Sdk.getCurrent().getTargets(); targets = Sdk.getCurrent().getTargets();
} }
mSdkTargetSelector.setTargets(targets);
mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/);
// If there's only one target, select it // If there's only one target, select it
if (targets != null && targets.length == 1) { if (targets != null && targets.length == 1) {
mSdkTargetSelector.setSelection(targets[0]); mSdkTargetSelector.setSelection(targets[0]);
} }
}
};
AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
// Invoke it once to initialize the targets
mSdkTargetChangeListener.onTargetsLoaded();
mSdkTargetSelector.setSelectionListener(new SelectionAdapter() { mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
@Override @Override
@@ -829,7 +860,7 @@ public class NewProjectCreationPage extends WizardPage {
String packageName = null; String packageName = null;
String activityName = null; String activityName = null;
int minSdkVersion = 0; // 0 means no minSdkVersion provided in the manifest int minSdkVersion = AndroidManifestParser.INVALID_MIN_SDK;
try { try {
packageName = manifestData.getPackage(); packageName = manifestData.getPackage();
minSdkVersion = manifestData.getApiLevelRequirement(); minSdkVersion = manifestData.getApiLevelRequirement();
@@ -927,7 +958,7 @@ public class NewProjectCreationPage extends WizardPage {
} }
} }
if (!foundTarget && minSdkVersion > 0) { if (!foundTarget && minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK) {
try { try {
for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { for (IAndroidTarget target : mSdkTargetSelector.getTargets()) {
if (target.getApiVersionNumber() == minSdkVersion) { if (target.getApiVersionNumber() == minSdkVersion) {
@@ -954,7 +985,8 @@ public class NewProjectCreationPage extends WizardPage {
if (!foundTarget) { if (!foundTarget) {
mInternalMinSdkVersionUpdate = true; mInternalMinSdkVersionUpdate = true;
mMinSdkVersionField.setText( mMinSdkVersionField.setText(
minSdkVersion <= 0 ? "" : Integer.toString(minSdkVersion)); //$NON-NLS-1$ minSdkVersion == AndroidManifestParser.INVALID_MIN_SDK ? "" :
Integer.toString(minSdkVersion)); //$NON-NLS-1$
mInternalMinSdkVersionUpdate = false; mInternalMinSdkVersionUpdate = false;
} }
} }
@@ -1148,7 +1180,7 @@ public class NewProjectCreationPage extends WizardPage {
return MSG_NONE; return MSG_NONE;
} }
int version = -1; int version = AndroidManifestParser.INVALID_MIN_SDK;
try { try {
// If not empty, it must be a valid integer > 0 // If not empty, it must be a valid integer > 0
version = Integer.parseInt(getMinSdkVersion()); version = Integer.parseInt(getMinSdkVersion());

View File

@@ -180,6 +180,8 @@ public class AndroidConstants {
public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ 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_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 = "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_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
public final static String CLASS_R = "android.R"; //$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$ public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
@@ -187,14 +189,16 @@ public class AndroidConstants {
public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$ public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$ public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$ public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
public final static String CLASS_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$ public final static String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
public final static String CLASS_VIEWGROUP_LAYOUTPARAMS = public final static String CLASS_VIEWGROUP_LAYOUTPARAMS =
CLASS_VIEWGROUP + "$" + CLASS_LAYOUTPARAMS; //$NON-NLS-1$ CLASS_VIEWGROUP + "$" + CLASS_NAME_LAYOUTPARAMS; //$NON-NLS-1$
public final static String CLASS_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$ public final static String CLASS_NAME_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
public final static String CLASS_FRAMELAYOUT =
"android.widget." + CLASS_NAME_FRAMELAYOUT; //$NON-NLS-1$
public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$ public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
public final static String CLASS_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$ public final static String CLASS_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
public final static String CLASS_PREFERENCES = public final static String CLASS_PREFERENCES =
"android.preference." + CLASS_PREFERENCE_SCREEN; //$NON-NLS-1$ "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; //$NON-NLS-1$
public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$ public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$ public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
@@ -215,4 +219,5 @@ public class AndroidConstants {
/** The base URL where to find the Android class & manifest documentation */ /** 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 CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$
public static final String LIBRARY_TEST_RUNNER = "android.test.runner"; // $NON-NLS-1$
} }

View File

@@ -16,6 +16,7 @@
package com.android.ide.eclipse.common.project; 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.AndroidConstants;
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkConstants;
@@ -33,6 +34,7 @@ import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException; import org.xml.sax.SAXParseException;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -72,6 +74,8 @@ public class AndroidManifestParser {
private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$
private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$
public final static int INVALID_MIN_SDK = -1;
/** /**
* XML error & data handler used when parsing the AndroidManifest.xml file. * XML error & data handler used when parsing the AndroidManifest.xml file.
* <p/> * <p/>
@@ -92,8 +96,9 @@ public class AndroidManifestParser {
private Set<String> mProcesses = null; private Set<String> mProcesses = null;
/** debuggable attribute value. If null, the attribute is not present. */ /** debuggable attribute value. If null, the attribute is not present. */
private Boolean mDebuggable = null; private Boolean mDebuggable = null;
/** API level requirement. if 0 the attribute was not present. */ /** API level requirement. if {@link AndroidManifestParser#INVALID_MIN_SDK}
private int mApiLevelRequirement = 0; * the attribute was not present. */
private int mApiLevelRequirement = INVALID_MIN_SDK;
/** List of all instrumentations declared by the manifest */ /** List of all instrumentations declared by the manifest */
private final ArrayList<String> mInstrumentations = new ArrayList<String>(); private final ArrayList<String> mInstrumentations = new ArrayList<String>();
/** List of all libraries in use declared by the manifest */ /** List of all libraries in use declared by the manifest */
@@ -171,7 +176,8 @@ public class AndroidManifestParser {
} }
/** /**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set. * Returns the <code>minSdkVersion</code> attribute, or
* {@link AndroidManifestParser#INVALID_MIN_SDK} if it's not set.
*/ */
int getApiLevelRequirement() { int getApiLevelRequirement() {
return mApiLevelRequirement; return mApiLevelRequirement;
@@ -252,11 +258,7 @@ public class AndroidManifestParser {
handleError(e, -1 /* lineNumber */); handleError(e, -1 /* lineNumber */);
} }
} else if (NODE_INSTRUMENTATION.equals(localName)) { } else if (NODE_INSTRUMENTATION.equals(localName)) {
value = getAttributeValue(attributes, ATTRIBUTE_NAME, processInstrumentationNode(attributes);
true /* hasNamespace */);
if (value != null) {
mInstrumentations.add(value);
}
} }
break; break;
case LEVEL_ACTIVITY: case LEVEL_ACTIVITY:
@@ -446,6 +448,25 @@ public class AndroidManifestParser {
} }
} }
/**
* 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. * Checks that a class is valid and can be used in the Android Manifest.
* <p/> * <p/>
@@ -481,7 +502,6 @@ public class AndroidManifestParser {
} }
} }
} }
} }
/** /**
@@ -561,20 +581,36 @@ public class AndroidManifestParser {
ManifestHandler manifestHandler = new ManifestHandler(manifestFile, ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
errorListener, gatherData, javaProject, markErrors); errorListener, gatherData, javaProject, markErrors);
parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); parser.parse(new InputSource(manifestFile.getContents()), manifestHandler);
// get the result from the handler // get the result from the handler
return new AndroidManifestParser(manifestHandler.getPackage(), return new AndroidManifestParser(manifestHandler.getPackage(),
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), manifestHandler.getActivities(),
manifestHandler.getProcesses(), manifestHandler.getDebuggable(), manifestHandler.getLauncherActivity(),
manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(), manifestHandler.getProcesses(),
manifestHandler.getDebuggable(),
manifestHandler.getApiLevelRequirement(),
manifestHandler.getInstrumentations(),
manifestHandler.getUsesLibraries()); manifestHandler.getUsesLibraries());
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"Bad parser configuration for %s: %s",
manifestFile.getFullPath(),
e.getMessage());
} catch (SAXException e) { } catch (SAXException e) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"Parser exception for %s: %s",
manifestFile.getFullPath(),
e.getMessage());
} catch (IOException e) { } catch (IOException e) {
} finally { // Don't log a console error when failing to read a non-existing file
if (!(e instanceof FileNotFoundException)) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"I/O error for %s: %s",
manifestFile.getFullPath(),
e.getMessage());
}
} }
return null; return null;
@@ -610,14 +646,31 @@ public class AndroidManifestParser {
// get the result from the handler // get the result from the handler
return new AndroidManifestParser(manifestHandler.getPackage(), return new AndroidManifestParser(manifestHandler.getPackage(),
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), manifestHandler.getActivities(),
manifestHandler.getProcesses(), manifestHandler.getDebuggable(), manifestHandler.getLauncherActivity(),
manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(), manifestHandler.getProcesses(),
manifestHandler.getDebuggable(),
manifestHandler.getApiLevelRequirement(),
manifestHandler.getInstrumentations(),
manifestHandler.getUsesLibraries()); manifestHandler.getUsesLibraries());
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"Bad parser configuration for %s: %s",
manifestFile.getAbsolutePath(),
e.getMessage());
} catch (SAXException e) { } catch (SAXException e) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"Parser exception for %s: %s",
manifestFile.getAbsolutePath(),
e.getMessage());
} catch (IOException e) { } catch (IOException e) {
} finally { // Don't log a console error when failing to read a non-existing file
if (!(e instanceof FileNotFoundException)) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"I/O error for %s: %s",
manifestFile.getAbsolutePath(),
e.getMessage());
}
} }
return null; return null;
@@ -642,10 +695,12 @@ public class AndroidManifestParser {
boolean gatherData, boolean gatherData,
boolean markErrors) boolean markErrors)
throws CoreException { throws CoreException {
IFile manifestFile = getManifest(javaProject.getProject());
try { try {
SAXParser parser = sParserFactory.newSAXParser(); SAXParser parser = sParserFactory.newSAXParser();
IFile manifestFile = getManifest(javaProject.getProject());
if (manifestFile != null) { if (manifestFile != null) {
ManifestHandler manifestHandler = new ManifestHandler(manifestFile, ManifestHandler manifestHandler = new ManifestHandler(manifestFile,
errorListener, gatherData, javaProject, markErrors); errorListener, gatherData, javaProject, markErrors);
@@ -660,9 +715,14 @@ public class AndroidManifestParser {
manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries()); manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries());
} }
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"Bad parser configuration for %s", manifestFile.getFullPath());
} catch (SAXException e) { } catch (SAXException e) {
AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"Parser exception for %s", manifestFile.getFullPath());
} catch (IOException e) { } catch (IOException e) {
} finally { AdtPlugin.logAndPrintError(e, AndroidManifestParser.class.getCanonicalName(),
"I/O error for %s", manifestFile.getFullPath());
} }
return null; return null;
@@ -750,7 +810,8 @@ public class AndroidManifestParser {
} }
/** /**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set. * Returns the <code>minSdkVersion</code> attribute, or {@link #INVALID_MIN_SDK}
* if it's not set.
*/ */
public int getApiLevelRequirement() { public int getApiLevelRequirement() {
return mApiLevelRequirement; return mApiLevelRequirement;

View File

@@ -16,8 +16,7 @@
package com.android.ide.eclipse.common.project; package com.android.ide.eclipse.common.project;
import com.android.ide.eclipse.common.project.BaseProjectHelper; import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.IJavaModel; import org.eclipse.jdt.core.IJavaModel;
@@ -107,4 +106,24 @@ public class ProjectChooserHelper {
return mAndroidProjects; return mAndroidProjects;
} }
/**
* Helper method to get the Android project with the given name
*
* @param projectName the name of the project to find
* @return the {@link IProject} for the Android project. <code>null</code> if not found.
*/
public IProject getAndroidProject(String projectName) {
IProject iproject = null;
IJavaProject[] javaProjects = getAndroidProjects(null);
if (javaProjects != null) {
for (IJavaProject javaProject : javaProjects) {
if (javaProject.getElementName().equals(projectName)) {
iproject = javaProject.getProject();
break;
}
}
}
return iproject;
}
} }

View File

@@ -70,6 +70,18 @@ public class DeclareStyleableInfo {
mFormats = formats; mFormats = formats;
} }
/**
* @param name The XML Name of the attribute
* @param formats The formats of the attribute. Cannot be null.
* Should have at least one format.
* @param javadoc Short javadoc (i.e. the first sentence).
*/
public AttributeInfo(String name, Format[] formats, String javadoc) {
mName = name;
mFormats = formats;
mJavaDoc = javadoc;
}
public AttributeInfo(AttributeInfo info) { public AttributeInfo(AttributeInfo info) {
mName = info.mName; mName = info.mName;
mFormats = info.mFormats; mFormats = info.mFormats;

View File

@@ -24,6 +24,7 @@ import com.android.sdklib.SdkConstants;
import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Image;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -221,6 +222,18 @@ public class ElementDescriptor {
mChildren = newChildren; mChildren = newChildren;
} }
/** Sets the list of allowed children.
* <p/>
* This is just a convenience method that converts a Collection into an array and
* calls {@link #setChildren(ElementDescriptor[])}.
* <p/>
* This means a <em>copy</em> of the collection is made. The collection is not
* stored by the recipient and can thus be altered by the caller.
*/
public void setChildren(Collection<ElementDescriptor> newChildren) {
setChildren(newChildren.toArray(new ElementDescriptor[newChildren.size()]));
}
/** /**
* Returns an optional tooltip. Will be null if not present. * Returns an optional tooltip. Will be null if not present.
* <p/> * <p/>

View File

@@ -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.ide.eclipse.editors.wizards.ConfigurationSelector.MobileCodeVerifier;
import com.android.layoutlib.api.ILayoutLog; import com.android.layoutlib.api.ILayoutLog;
import com.android.layoutlib.api.ILayoutResult; import com.android.layoutlib.api.ILayoutResult;
import com.android.layoutlib.api.IProjectCallback;
import com.android.layoutlib.api.IResourceValue; import com.android.layoutlib.api.IResourceValue;
import com.android.layoutlib.api.IStyleResourceValue; import com.android.layoutlib.api.IStyleResourceValue;
import com.android.layoutlib.api.IXmlPullParser;
import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo;
import com.android.sdklib.IAndroidTarget; import com.android.sdklib.IAndroidTarget;
@@ -222,7 +224,7 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
// updateUiFromFramework will reset language/region combo, so we must call // updateUiFromFramework will reset language/region combo, so we must call
// setConfiguration after, or the settext on language/region will be lost. // setConfiguration after, or the settext on language/region will be lost.
if (mEditedConfig != null) { if (mEditedConfig != null) {
setConfiguration(mEditedConfig); setConfiguration(mEditedConfig, false /*force*/);
} }
// make sure we remove the custom view loader, since its parent class loader is the // 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 @Override
void editNewFile(FolderConfiguration configuration) { void editNewFile(FolderConfiguration configuration) {
// update the configuration UI // update the configuration UI
setConfiguration(configuration); setConfiguration(configuration, true /*force*/);
// enable the create button if the current and edited config are not equals // enable the create button if the current and edited config are not equals
mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false); mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
@@ -976,17 +978,13 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (themeIndex != -1) { if (themeIndex != -1) {
String theme = mThemeCombo.getItem(themeIndex); 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. // Render a single object as described by the ViewElementDescriptor.
WidgetPullParser parser = new WidgetPullParser(descriptor); WidgetPullParser parser = new WidgetPullParser(descriptor);
ILayoutResult result = bridge.bridge.computeLayout(parser, ILayoutResult result = computeLayout(bridge, parser,
null /* projectKey */, 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, configuredProjectResources, frameworkResources, projectCallback,
null /* logger */); null /* logger */);
@@ -1073,11 +1071,14 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
/** /**
* Update the UI controls state with a given {@link FolderConfiguration}. * Update the UI controls state with a given {@link FolderConfiguration}.
* <p/>If a qualifier is not present in the {@link FolderConfiguration} object, the UI control * <p/>If <var>force</var> is set to <code>true</code> the UI will be changed to exactly reflect
* is not modified. However if the value in the control is not the default value, a warning * <var>config</var>, otherwise, if a qualifier is not present in <var>config</var>,
* icon is showed. * 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. mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets.
mEditedConfig = config; mEditedConfig = config;
@@ -1088,6 +1089,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (countryQualifier != null) { if (countryQualifier != null) {
mCountry.setText(String.format("%1$d", countryQualifier.getCode())); mCountry.setText(String.format("%1$d", countryQualifier.getCode()));
mCurrentConfig.setCountryCodeQualifier(countryQualifier); mCurrentConfig.setCountryCodeQualifier(countryQualifier);
} else if (force) {
mCountry.setText(""); //$NON-NLS-1$
mCurrentConfig.setCountryCodeQualifier(null);
} else if (mCountry.getText().length() > 0) { } else if (mCountry.getText().length() > 0) {
mCountryIcon.setImage(mWarningImage); mCountryIcon.setImage(mWarningImage);
} }
@@ -1097,6 +1101,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (networkQualifier != null) { if (networkQualifier != null) {
mNetwork.setText(String.format("%1$d", networkQualifier.getCode())); mNetwork.setText(String.format("%1$d", networkQualifier.getCode()));
mCurrentConfig.setNetworkCodeQualifier(networkQualifier); mCurrentConfig.setNetworkCodeQualifier(networkQualifier);
} else if (force) {
mNetwork.setText(""); //$NON-NLS-1$
mCurrentConfig.setNetworkCodeQualifier(null);
} else if (mNetwork.getText().length() > 0) { } else if (mNetwork.getText().length() > 0) {
mNetworkIcon.setImage(mWarningImage); mNetworkIcon.setImage(mWarningImage);
} }
@@ -1106,6 +1113,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (languageQualifier != null) { if (languageQualifier != null) {
mLanguage.setText(languageQualifier.getValue()); mLanguage.setText(languageQualifier.getValue());
mCurrentConfig.setLanguageQualifier(languageQualifier); mCurrentConfig.setLanguageQualifier(languageQualifier);
} else if (force) {
mLanguage.setText(""); //$NON-NLS-1$
mCurrentConfig.setLanguageQualifier(null);
} else if (mLanguage.getText().length() > 0) { } else if (mLanguage.getText().length() > 0) {
mLanguageIcon.setImage(mWarningImage); mLanguageIcon.setImage(mWarningImage);
} }
@@ -1115,6 +1125,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (regionQualifier != null) { if (regionQualifier != null) {
mRegion.setText(regionQualifier.getValue()); mRegion.setText(regionQualifier.getValue());
mCurrentConfig.setRegionQualifier(regionQualifier); mCurrentConfig.setRegionQualifier(regionQualifier);
} else if (force) {
mRegion.setText(""); //$NON-NLS-1$
mCurrentConfig.setRegionQualifier(null);
} else if (mRegion.getText().length() > 0) { } else if (mRegion.getText().length() > 0) {
mRegionIcon.setImage(mWarningImage); mRegionIcon.setImage(mWarningImage);
} }
@@ -1125,6 +1138,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
mOrientation.select( mOrientation.select(
ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1); ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1);
mCurrentConfig.setScreenOrientationQualifier(orientationQualifier); mCurrentConfig.setScreenOrientationQualifier(orientationQualifier);
} else if (force) {
mOrientation.select(0);
mCurrentConfig.setScreenOrientationQualifier(null);
} else if (mOrientation.getSelectionIndex() != 0) { } else if (mOrientation.getSelectionIndex() != 0) {
mOrientationIcon.setImage(mWarningImage); mOrientationIcon.setImage(mWarningImage);
} }
@@ -1134,6 +1150,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (densityQualifier != null) { if (densityQualifier != null) {
mDensity.setText(String.format("%1$d", densityQualifier.getValue())); mDensity.setText(String.format("%1$d", densityQualifier.getValue()));
mCurrentConfig.setPixelDensityQualifier(densityQualifier); mCurrentConfig.setPixelDensityQualifier(densityQualifier);
} else if (force) {
mDensity.setText(""); //$NON-NLS-1$
mCurrentConfig.setPixelDensityQualifier(null);
} else if (mDensity.getText().length() > 0) { } else if (mDensity.getText().length() > 0) {
mDensityIcon.setImage(mWarningImage); mDensityIcon.setImage(mWarningImage);
} }
@@ -1143,6 +1162,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (touchQualifier != null) { if (touchQualifier != null) {
mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1); mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1);
mCurrentConfig.setTouchTypeQualifier(touchQualifier); mCurrentConfig.setTouchTypeQualifier(touchQualifier);
} else if (force) {
mTouch.select(0);
mCurrentConfig.setTouchTypeQualifier(null);
} else if (mTouch.getSelectionIndex() != 0) { } else if (mTouch.getSelectionIndex() != 0) {
mTouchIcon.setImage(mWarningImage); mTouchIcon.setImage(mWarningImage);
} }
@@ -1152,6 +1174,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (keyboardQualifier != null) { if (keyboardQualifier != null) {
mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1); mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1);
mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier); mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier);
} else if (force) {
mKeyboard.select(0);
mCurrentConfig.setKeyboardStateQualifier(null);
} else if (mKeyboard.getSelectionIndex() != 0) { } else if (mKeyboard.getSelectionIndex() != 0) {
mKeyboardIcon.setImage(mWarningImage); mKeyboardIcon.setImage(mWarningImage);
} }
@@ -1161,6 +1186,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
if (inputQualifier != null) { if (inputQualifier != null) {
mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1); mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1);
mCurrentConfig.setTextInputMethodQualifier(inputQualifier); mCurrentConfig.setTextInputMethodQualifier(inputQualifier);
} else if (force) {
mTextInput.select(0);
mCurrentConfig.setTextInputMethodQualifier(null);
} else if (mTextInput.getSelectionIndex() != 0) { } else if (mTextInput.getSelectionIndex() != 0) {
mTextInputIcon.setImage(mWarningImage); mTextInputIcon.setImage(mWarningImage);
} }
@@ -1171,6 +1199,9 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
mNavigation.select( mNavigation.select(
NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1); NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1);
mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter); mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter);
} else if (force) {
mNavigation.select(0);
mCurrentConfig.setNavigationMethodQualifier(null);
} else if (mNavigation.getSelectionIndex() != 0) { } else if (mNavigation.getSelectionIndex() != 0) {
mNavigationIcon.setImage(mWarningImage); mNavigationIcon.setImage(mWarningImage);
} }
@@ -1181,6 +1212,10 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
mSize1.setText(String.format("%1$d", sizeQualifier.getValue1())); mSize1.setText(String.format("%1$d", sizeQualifier.getValue1()));
mSize2.setText(String.format("%1$d", sizeQualifier.getValue2())); mSize2.setText(String.format("%1$d", sizeQualifier.getValue2()));
mCurrentConfig.setScreenDimensionQualifier(sizeQualifier); 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) { } else if (mSize1.getText().length() > 0 && mSize2.getText().length() > 0) {
mSizeIcon.setImage(mWarningImage); mSizeIcon.setImage(mWarningImage);
} }
@@ -1607,7 +1642,7 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
// at this point, we have not opened a new file. // at this point, we have not opened a new file.
// update the configuration icons with the new edited config. // 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 // enable the create button if the current and edited config are not equals
mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false); mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false);
@@ -1794,44 +1829,15 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
// Compute the layout // Compute the layout
UiElementPullParser parser = new UiElementPullParser(getModel()); UiElementPullParser parser = new UiElementPullParser(getModel());
Rectangle rect = getBounds(); 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). // FIXME pass the density/dpi from somewhere (resource config or skin).
result = bridge.bridge.computeLayout(parser, ILayoutResult result = computeLayout(bridge, parser,
iProject /* projectKey */, iProject /* projectKey */,
rect.width, rect.height, 160, 160.f, 160.f, rect.width, rect.height, 160, 160.f, 160.f,
theme, isProjectTheme, theme, isProjectTheme,
mConfiguredProjectRes, frameworkResources, mProjectCallback, mConfiguredProjectRes, frameworkResources, mProjectCallback,
mLogger); mLogger);
} else if (bridge.apiLevel == 2) {
// api with boolean for separation of project/framework theme
boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
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. // update the UiElementNode with the layout info.
if (result.getSuccess() == ILayoutResult.SUCCESS) { if (result.getSuccess() == ILayoutResult.SUCCESS) {
@@ -2383,4 +2389,48 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
return null; 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<String, Map<String, IResourceValue>> projectResources,
Map<String, Map<String, IResourceValue>> 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);
}
}
} }

View File

@@ -43,7 +43,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
public static final String ID_ATTR = "id"; //$NON-NLS-1$ public static final String ID_ATTR = "id"; //$NON-NLS-1$
/** The document descriptor. Contains all layouts and views linked together. */ /** The document descriptor. Contains all layouts and views linked together. */
private DocumentDescriptor mDescriptor = private DocumentDescriptor mRootDescriptor =
new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$ new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$
/** The list of all known ViewLayout descriptors. */ /** The list of all known ViewLayout descriptors. */
@@ -60,7 +60,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
/** @return the document descriptor. Contains all layouts and views linked together. */ /** @return the document descriptor. Contains all layouts and views linked together. */
public DocumentDescriptor getDescriptor() { public DocumentDescriptor getDescriptor() {
return mDescriptor; return mRootDescriptor;
} }
/** @return The read-only list of all known ViewLayout descriptors. */ /** @return The read-only list of all known ViewLayout descriptors. */
@@ -74,7 +74,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
} }
public ElementDescriptor[] getRootElementDescriptors() { public ElementDescriptor[] getRootElementDescriptors() {
return mDescriptor.getChildren(); return mRootDescriptor.getChildren();
} }
/** /**
@@ -98,6 +98,10 @@ public final class LayoutDescriptors implements IDescriptorProvider {
} }
} }
// Create <include> as a synthetic regular view.
// Note: ViewStub is already described by attrs.xml
insertInclude(newViews);
ArrayList<ElementDescriptor> newLayouts = new ArrayList<ElementDescriptor>(); ArrayList<ElementDescriptor> newLayouts = new ArrayList<ElementDescriptor>();
if (layouts != null) { if (layouts != null) {
for (ViewClassInfo info : layouts) { for (ViewClassInfo info : layouts) {
@@ -109,17 +113,22 @@ public final class LayoutDescriptors implements IDescriptorProvider {
ArrayList<ElementDescriptor> newDescriptors = new ArrayList<ElementDescriptor>(); ArrayList<ElementDescriptor> newDescriptors = new ArrayList<ElementDescriptor>();
newDescriptors.addAll(newLayouts); newDescriptors.addAll(newLayouts);
newDescriptors.addAll(newViews); newDescriptors.addAll(newViews);
ElementDescriptor[] newArray = newDescriptors.toArray(
new ElementDescriptor[newDescriptors.size()]);
// Link all layouts to everything else here.. recursively // Link all layouts to everything else here.. recursively
for (ElementDescriptor layoutDesc : newLayouts) { for (ElementDescriptor layoutDesc : newLayouts) {
layoutDesc.setChildren(newArray); layoutDesc.setChildren(newDescriptors);
} }
// The <merge> tag can only be a root tag, so it is added at the end.
// It gets everything else as children but it is not made a child itself.
ElementDescriptor mergeTag = createMerge(newLayouts);
mergeTag.setChildren(newDescriptors); // mergeTag makes a copy of the list
newDescriptors.add(mergeTag);
newLayouts.add(mergeTag);
mViewDescriptors = newViews; mViewDescriptors = newViews;
mLayoutDescriptors = newLayouts; mLayoutDescriptors = newLayouts;
mDescriptor.setChildren(newArray); mRootDescriptor.setChildren(newDescriptors);
mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors); mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors);
mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors); mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors);
@@ -186,7 +195,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
if (need_separator) { if (need_separator) {
String title; String title;
if (layoutParams.getShortClassName().equals( if (layoutParams.getShortClassName().equals(
AndroidConstants.CLASS_LAYOUTPARAMS)) { AndroidConstants.CLASS_NAME_LAYOUTPARAMS)) {
title = String.format("Layout Attributes from %1$s", title = String.format("Layout Attributes from %1$s",
layoutParams.getViewLayoutClass().getShortClassName()); layoutParams.getViewLayoutClass().getShortClassName());
} else { } else {
@@ -217,4 +226,99 @@ public final class LayoutDescriptors implements IDescriptorProvider {
false /* mandatory */); false /* mandatory */);
} }
/**
* Creates a new <include> descriptor and adds it to the list of view descriptors.
*
* @param knownViews A list of view descriptors being populated. Also used to find the
* View descriptor and extract its layout attributes.
*/
private void insertInclude(ArrayList<ElementDescriptor> knownViews) {
String xml_name = "include"; //$NON-NLS-1$
// Create the include custom attributes
ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
// Note that the "layout" attribute does NOT have the Android namespace
DescriptorsUtils.appendAttribute(attributes,
null, //elementXmlName
null, //nsUri
new AttributeInfo(
"layout", //$NON-NLS-1$
new AttributeInfo.Format[] { AttributeInfo.Format.REFERENCE }
),
true, //required
null); //overrides
DescriptorsUtils.appendAttribute(attributes,
null, //elementXmlName
SdkConstants.NS_RESOURCES, //nsUri
new AttributeInfo(
"id", //$NON-NLS-1$
new AttributeInfo.Format[] { AttributeInfo.Format.REFERENCE }
),
true, //required
null); //overrides
// Find View and inherit all its layout attributes
AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes(
AndroidConstants.CLASS_VIEW, knownViews);
// Create the include descriptor
ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name
xml_name, // ui_name
null, // canonical class name, we don't have one
"Lets you statically include XML layouts inside other XML layouts.", // tooltip
null, // sdk_url
attributes.toArray(new AttributeDescriptor[attributes.size()]),
viewLayoutAttribs, // layout attributes
null, // children
false /* mandatory */);
knownViews.add(desc);
}
/**
* Creates and return a new <merge> descriptor.
* @param knownLayouts A list of all known layout view descriptors, used to find the
* FrameLayout descriptor and extract its layout attributes.
*/
private ElementDescriptor createMerge(ArrayList<ElementDescriptor> knownLayouts) {
String xml_name = "merge"; //$NON-NLS-1$
// Find View and inherit all its layout attributes
AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes(
AndroidConstants.CLASS_FRAMELAYOUT, knownLayouts);
// Create the include descriptor
ViewElementDescriptor desc = new ViewElementDescriptor(xml_name, // xml_name
xml_name, // ui_name
null, // canonical class name, we don't have one
"A root tag useful for XML layouts inflated using a ViewStub.", // tooltip
null, // sdk_url
null, // attributes
viewLayoutAttribs, // layout attributes
null, // children
false /* mandatory */);
return desc;
}
/**
* Finds the descriptor and retrieves all its layout attributes.
*/
private AttributeDescriptor[] findViewLayoutAttributes(
String viewFqcn,
ArrayList<ElementDescriptor> knownViews) {
for (ElementDescriptor desc : knownViews) {
if (desc instanceof ViewElementDescriptor) {
ViewElementDescriptor viewDesc = (ViewElementDescriptor) desc;
if (viewFqcn.equals(viewDesc.getCanonicalClassName())) {
return viewDesc.getLayoutAttributes();
}
}
}
return null;
}
} }

View File

@@ -96,6 +96,8 @@ class DropFeedback {
RelativeInfo info = null; RelativeInfo info = null;
UiElementEditPart sibling = null; UiElementEditPart sibling = null;
// TODO consider merge like a vertical layout
// TODO consider TableLayout like a linear
if (LayoutConstants.LINEAR_LAYOUT.equals(layoutXmlName)) { if (LayoutConstants.LINEAR_LAYOUT.equals(layoutXmlName)) {
sibling = findLinearTarget(parentPart, where)[1]; sibling = findLinearTarget(parentPart, where)[1];

View File

@@ -81,7 +81,7 @@ public class UiViewElementNode extends UiElementNode {
if (layoutDescriptors != null) { if (layoutDescriptors != null) {
for (ElementDescriptor desc : layoutDescriptors) { for (ElementDescriptor desc : layoutDescriptors) {
if (desc instanceof ViewElementDescriptor && if (desc instanceof ViewElementDescriptor &&
desc.getXmlName().equals(AndroidConstants.CLASS_FRAMELAYOUT)) { desc.getXmlName().equals(AndroidConstants.CLASS_NAME_FRAMELAYOUT)) {
layout_attrs = ((ViewElementDescriptor) desc).getLayoutAttributes(); layout_attrs = ((ViewElementDescriptor) desc).getLayoutAttributes();
need_xmlns = true; need_xmlns = true;
break; break;

View File

@@ -1234,7 +1234,8 @@ public class UiElementNode implements IPropertySource {
if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$ if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$
String uri = attr.getNodeValue(); String uri = attr.getNodeValue();
String nsPrefix = attr.getLocalName(); String nsPrefix = attr.getLocalName();
if (SdkConstants.NS_RESOURCES.equals(uri)) { // Is this the URI we are looking for? If yes, we found its prefix.
if (nsUri.equals(uri)) {
return nsPrefix; return nsPrefix;
} }
visited.add(nsPrefix); visited.add(nsPrefix);
@@ -1244,7 +1245,8 @@ public class UiElementNode implements IPropertySource {
// Use a sensible default prefix if we can't find one. // Use a sensible default prefix if we can't find one.
// We need to make sure the prefix is not one that was declared in the scope // We need to make sure the prefix is not one that was declared in the scope
// visited above. // visited above. Use a default namespace prefix "android" for the Android resource
// NS and use "ns" for all other custom namespaces.
String prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$ String prefix = SdkConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$
String base = prefix; String base = prefix;
for (int i = 1; visited.contains(prefix); i++) { for (int i = 1; visited.contains(prefix); i++) {
@@ -1477,16 +1479,16 @@ public class UiElementNode implements IPropertySource {
} }
if (attribute != null) { if (attribute != null) {
final UiAttributeNode fAttribute = attribute;
// get the current value and compare it to the new value // get the current value and compare it to the new value
String oldValue = fAttribute.getCurrentValue(); String oldValue = attribute.getCurrentValue();
final String newValue = (String)value; final String newValue = (String)value;
if (oldValue.equals(newValue)) { if (oldValue.equals(newValue)) {
return; return;
} }
final UiAttributeNode fAttribute = attribute;
AndroidEditor editor = getEditor(); AndroidEditor editor = getEditor();
editor.editXmlModel(new Runnable() { editor.editXmlModel(new Runnable() {
public void run() { public void run() {
@@ -1495,6 +1497,4 @@ public class UiElementNode implements IPropertySource {
}); });
} }
} }
} }

View File

@@ -17,8 +17,10 @@
package com.android.ide.eclipse.editors.wizards; package com.android.ide.eclipse.editors.wizards;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.ProjectChooserHelper; import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor;
@@ -39,6 +41,7 @@ import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.IStructuredSelection;
@@ -235,7 +238,7 @@ class NewXmlFileCreationPage extends WizardPage {
"An XML file that describes preferences.", // tooltip "An XML file that describes preferences.", // tooltip
ResourceFolderType.XML, // folder type ResourceFolderType.XML, // folder type
AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed
AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root AndroidConstants.CLASS_NAME_PREFERENCE_SCREEN, // default root
SdkConstants.NS_RESOURCES, // xmlns SdkConstants.NS_RESOURCES, // xmlns
null, // default attributes null, // default attributes
1 // target API level 1 // target API level
@@ -271,9 +274,9 @@ class NewXmlFileCreationPage extends WizardPage {
final static int NUM_COL = 4; final static int NUM_COL = 4;
/** Absolute destination folder root, e.g. "/res/" */ /** Absolute destination folder root, e.g. "/res/" */
private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; private static final String RES_FOLDER_ABS = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP;
/** Relative destination folder root, e.g. "res/" */ /** Relative destination folder root, e.g. "res/" */
private static String sResFolderRel = SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP; private static final String RES_FOLDER_REL = SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
private IProject mProject; private IProject mProject;
private Text mProjectTextField; private Text mProjectTextField;
@@ -288,7 +291,9 @@ class NewXmlFileCreationPage extends WizardPage {
private boolean mInternalTypeUpdate; private boolean mInternalTypeUpdate;
private boolean mInternalConfigSelectorUpdate; private boolean mInternalConfigSelectorUpdate;
private ProjectChooserHelper mProjectChooserHelper; private ProjectChooserHelper mProjectChooserHelper;
private ITargetChangeListener mSdkTargetChangeListener;
private TypeInfo mCurrentTypeInfo;
// --- UI creation --- // --- UI creation ---
@@ -335,9 +340,44 @@ class NewXmlFileCreationPage extends WizardPage {
initializeFromSelection(mInitialSelection); initializeFromSelection(mInitialSelection);
initializeRootValues(); initializeRootValues();
enableTypesBasedOnApi(); enableTypesBasedOnApi();
if (mCurrentTypeInfo != null) {
updateRootCombo(mCurrentTypeInfo);
}
installTargetChangeListener();
validatePage(); validatePage();
} }
private void installTargetChangeListener() {
mSdkTargetChangeListener = new ITargetChangeListener() {
public void onProjectTargetChange(IProject changedProject) {
// If this is the current project, force it to reload its data
if (changedProject != null && changedProject == mProject) {
changeProject(mProject);
}
}
public void onTargetsLoaded() {
// Reload the current project, if any, in case its target has changed.
if (mProject != null) {
changeProject(mProject);
}
}
};
AdtPlugin.getDefault().addTargetListener(mSdkTargetChangeListener);
}
@Override
public void dispose() {
if (mSdkTargetChangeListener != null) {
AdtPlugin.getDefault().removeTargetListener(mSdkTargetChangeListener);
mSdkTargetChangeListener = null;
}
super.dispose();
}
/** /**
* Returns the target project or null. * Returns the target project or null.
*/ */
@@ -650,7 +690,6 @@ class NewXmlFileCreationPage extends WizardPage {
return; return;
} }
// Find the best match in the element list. In case there are multiple selected elements // Find the best match in the element list. In case there are multiple selected elements
// select the one that provides the most information and assign them a score, // select the one that provides the most information and assign them a score,
// e.g. project=1 + folder=2 + file=4. // e.g. project=1 + folder=2 + file=4.
@@ -745,8 +784,35 @@ class NewXmlFileCreationPage extends WizardPage {
// cleared above. // cleared above.
// get the AndroidTargetData from the project // get the AndroidTargetData from the project
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); IAndroidTarget target = null;
AndroidTargetData data = Sdk.getCurrent().getTargetData(target); AndroidTargetData data = null;
target = Sdk.getCurrent().getTarget(mProject);
if (target == null) {
// A project should have a target. The target can be missing if the project
// is an old project for which a target hasn't been affected or if the
// target no longer exists in this SDK. Simply log the error and dismiss.
AdtPlugin.log(IStatus.INFO,
"NewXmlFile wizard: no platform target for project %s", //$NON-NLS-1$
mProject.getName());
continue;
} else {
data = Sdk.getCurrent().getTargetData(target);
if (data == null) {
// We should have both a target and its data.
// However if the wizard is invoked whilst the platform is still being
// loaded we can end up in a weird case where we have a target but it
// doesn't have any data yet.
// Lets log a warning and silently ignore this root.
AdtPlugin.log(IStatus.INFO,
"NewXmlFile wizard: no data for target %s, project %s", //$NON-NLS-1$
target.getName(), mProject.getName());
continue;
}
}
IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed); IDescriptorProvider provider = data.getDescriptorProvider((Integer)rootSeed);
ElementDescriptor descriptor = provider.getDescriptor(); ElementDescriptor descriptor = provider.getDescriptor();
@@ -819,6 +885,10 @@ class NewXmlFileCreationPage extends WizardPage {
/** /**
* Changes mProject to the given new project and update the UI accordingly. * Changes mProject to the given new project and update the UI accordingly.
* <p/>
* Note that this does not check if the new project is the same as the current one
* on purpose, which allows a project to be updated when its target has changed or
* when targets are loaded in the background.
*/ */
private void changeProject(IProject newProject) { private void changeProject(IProject newProject) {
mProject = newProject; mProject = newProject;
@@ -856,16 +926,16 @@ class NewXmlFileCreationPage extends WizardPage {
ArrayList<TypeInfo> matches = new ArrayList<TypeInfo>(); ArrayList<TypeInfo> matches = new ArrayList<TypeInfo>();
// We get "res/foo" from selections relative to the project when we want a "/res/foo" path. // We get "res/foo" from selections relative to the project when we want a "/res/foo" path.
if (wsFolderPath.startsWith(sResFolderRel)) { if (wsFolderPath.startsWith(RES_FOLDER_REL)) {
wsFolderPath = sResFolderAbs + wsFolderPath.substring(sResFolderRel.length()); wsFolderPath = RES_FOLDER_ABS + wsFolderPath.substring(RES_FOLDER_REL.length());
mInternalWsFolderPathUpdate = true; mInternalWsFolderPathUpdate = true;
mWsFolderPathTextField.setText(wsFolderPath); mWsFolderPathTextField.setText(wsFolderPath);
mInternalWsFolderPathUpdate = false; mInternalWsFolderPathUpdate = false;
} }
if (wsFolderPath.startsWith(sResFolderAbs)) { if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
wsFolderPath = wsFolderPath.substring(sResFolderAbs.length()); wsFolderPath = wsFolderPath.substring(RES_FOLDER_ABS.length());
int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR); int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR);
if (pos >= 0) { if (pos >= 0) {
@@ -952,16 +1022,16 @@ class NewXmlFileCreationPage extends WizardPage {
// The configuration is valid. Reformat the folder path using the canonical // The configuration is valid. Reformat the folder path using the canonical
// value from the configuration. // value from the configuration.
newPath = sResFolderAbs + mTempConfig.getFolderName(type.getResFolderType()); newPath = RES_FOLDER_ABS + mTempConfig.getFolderName(type.getResFolderType());
} else { } else {
// The configuration is invalid. We still update the path but this time // The configuration is invalid. We still update the path but this time
// do it manually on the string. // do it manually on the string.
if (wsFolderPath.startsWith(sResFolderAbs)) { if (wsFolderPath.startsWith(RES_FOLDER_ABS)) {
wsFolderPath.replaceFirst( wsFolderPath.replaceFirst(
"^(" + sResFolderAbs +")[^-]*(.*)", //$NON-NLS-1$ //$NON-NLS-2$ "^(" + RES_FOLDER_ABS +")[^-]*(.*)", //$NON-NLS-1$ //$NON-NLS-2$
"\\1" + type.getResFolderName() + "\\2"); //$NON-NLS-1$ //$NON-NLS-2$ "\\1" + type.getResFolderName() + "\\2"); //$NON-NLS-1$ //$NON-NLS-2$
} else { } else {
newPath = sResFolderAbs + mTempConfig.getFolderName(type.getResFolderType()); newPath = RES_FOLDER_ABS + mTempConfig.getFolderName(type.getResFolderType());
} }
} }
@@ -1018,7 +1088,7 @@ class NewXmlFileCreationPage extends WizardPage {
if (type != null) { if (type != null) {
mConfigSelector.getConfiguration(mTempConfig); mConfigSelector.getConfiguration(mTempConfig);
StringBuffer sb = new StringBuffer(sResFolderAbs); StringBuffer sb = new StringBuffer(RES_FOLDER_ABS);
sb.append(mTempConfig.getFolderName(type.getResFolderType())); sb.append(mTempConfig.getFolderName(type.getResFolderType()));
mInternalWsFolderPathUpdate = true; mInternalWsFolderPathUpdate = true;
@@ -1038,6 +1108,7 @@ class NewXmlFileCreationPage extends WizardPage {
private void selectType(TypeInfo type) { private void selectType(TypeInfo type) {
if (type == null || !type.getWidget().getSelection()) { if (type == null || !type.getWidget().getSelection()) {
mInternalTypeUpdate = true; mInternalTypeUpdate = true;
mCurrentTypeInfo = type;
for (TypeInfo type2 : sTypes) { for (TypeInfo type2 : sTypes) {
type2.getWidget().setSelection(type2 == type); type2.getWidget().setSelection(type2 == type);
} }
@@ -1131,8 +1202,8 @@ class NewXmlFileCreationPage extends WizardPage {
// -- validate generated path // -- validate generated path
if (error == null) { if (error == null) {
String wsFolderPath = getWsFolderPath(); String wsFolderPath = getWsFolderPath();
if (!wsFolderPath.startsWith(sResFolderAbs)) { if (!wsFolderPath.startsWith(RES_FOLDER_ABS)) {
error = String.format("Target folder must start with %1$s.", sResFolderAbs); error = String.format("Target folder must start with %1$s.", RES_FOLDER_ABS);
} }
} }

View File

@@ -15,7 +15,13 @@
*/ */
package com.android.ide.eclipse.tests; 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.File;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -45,12 +51,29 @@ public class AdtTestData {
// accessed normally // accessed normally
mOsRootDataPath = System.getProperty("test_data"); mOsRootDataPath = System.getProperty("test_data");
if (mOsRootDataPath == null) { 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$ URL url = this.getClass().getClassLoader().getResource("data"); //$NON-NLS-1$
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(); 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)) { 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); mOsRootDataPath = mOsRootDataPath.concat(File.separator);
} }
} }

View File

@@ -16,16 +16,19 @@
package com.android.ide.eclipse.common.project; 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} * Tests for {@link AndroidManifestParser}
*/ */
public class AndroidManifestParserTest extends TestCase { public class AndroidManifestParserTest extends TestCase {
private AndroidManifestParser mManifest; private AndroidManifestParser mManifestTestApp;
private AndroidManifestParser mManifestInstrumentation;
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 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 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 LIBRARY_NAME = "android.test.runner"; //$NON-NLS-1$
@@ -35,60 +38,46 @@ public class AndroidManifestParserTest extends TestCase {
protected void setUp() throws Exception { protected void setUp() throws Exception {
super.setUp(); super.setUp();
// create the test data String testFilePath = AdtTestData.getInstance().getTestFilePath(
StringBuilder sb = new StringBuilder(); TESTAPP_XML);
sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); //$NON-NLS-1$ mManifestTestApp = AndroidManifestParser.parseForData(testFilePath);
sb.append("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"); //$NON-NLS-1$ assertNotNull(mManifestTestApp);
sb.append(" package=\""); //$NON-NLS-1$
sb.append(PACKAGE_NAME);
sb.append("\">\n"); //$NON-NLS-1$
sb.append(" <application android:icon=\"@drawable/icon\">\n"); //$NON-NLS-1$
sb.append(" <activity android:name=\""); //$NON-NLS-1$
sb.append(ACTIVITY_NAME);
sb.append("\" android:label=\"@string/app_name\">\n"); //$NON-NLS-1$
sb.append(" <intent-filter>\n"); //$NON-NLS-1$
sb.append(" <action android:name=\"android.intent.action.MAIN\" />\n"); //$NON-NLS-1$
sb.append(" <category android:name=\"android.intent.category.LAUNCHER\" />\"\n"); //$NON-NLS-1$
sb.append(" <category android:name=\"android.intent.category.DEFAULT\" />\n"); //$NON-NLS-1$
sb.append(" </intent-filter>\n"); //$NON-NLS-1$
sb.append(" </activity>\n"); //$NON-NLS-1$
sb.append(" <uses-library android:name=\""); //$NON-NLS-1$
sb.append(LIBRARY_NAME);
sb.append("\" />\n"); //$NON-NLS-1$
sb.append(" </application>"); //$NON-NLS-1$
sb.append(" <instrumentation android:name=\""); //$NON-NLS-1$
sb.append(INSTRUMENTATION_NAME);
sb.append("\"\n");
sb.append(" android:targetPackage=\"com.example.android.apis\"\n");
sb.append(" android:label=\"Tests for Api Demos.\"/>\n");
sb.append("</manifest>\n"); //$NON-NLS-1$
FileMock mockFile = new FileMock("AndroidManifest.xml", sb.toString().getBytes()); testFilePath = AdtTestData.getInstance().getTestFilePath(
INSTRUMENTATION_XML);
mManifestInstrumentation = AndroidManifestParser.parseForData(testFilePath);
assertNotNull(mManifestInstrumentation);
}
mManifest = AndroidManifestParser.parseForData(mockFile); public void testGetInstrumentationInformation() {
assertNotNull(mManifest); assertEquals(1, mManifestInstrumentation.getInstrumentations().length);
assertEquals(INSTRUMENTATION_NAME, mManifestTestApp.getInstrumentations()[0]);
} }
public void testGetPackage() { public void testGetPackage() {
assertEquals("com.android.testapp", mManifest.getPackage()); assertEquals(PACKAGE_NAME, mManifestTestApp.getPackage());
} }
public void testGetActivities() { public void testGetActivities() {
assertEquals(1, mManifest.getActivities().length); assertEquals(1, mManifestTestApp.getActivities().length);
assertEquals(ACTIVITY_NAME, mManifest.getActivities()[0]); assertEquals(ACTIVITY_NAME, mManifestTestApp.getActivities()[0]);
} }
public void testGetLauncherActivity() { public void testGetLauncherActivity() {
assertEquals(ACTIVITY_NAME, mManifest.getLauncherActivity()); assertEquals(ACTIVITY_NAME, mManifestTestApp.getLauncherActivity());
} }
public void testGetUsesLibraries() { public void testGetUsesLibraries() {
assertEquals(1, mManifest.getUsesLibraries().length); assertEquals(1, mManifestTestApp.getUsesLibraries().length);
assertEquals(LIBRARY_NAME, mManifest.getUsesLibraries()[0]); assertEquals(LIBRARY_NAME, mManifestTestApp.getUsesLibraries()[0]);
} }
public void testGetInstrumentations() { public void testGetInstrumentations() {
assertEquals(1, mManifest.getInstrumentations().length); assertEquals(1, mManifestTestApp.getInstrumentations().length);
assertEquals(INSTRUMENTATION_NAME, mManifest.getInstrumentations()[0]); assertEquals(INSTRUMENTATION_NAME, mManifestTestApp.getInstrumentations()[0]);
}
public void testGetPackageName() {
assertEquals(PACKAGE_NAME, mManifestTestApp.getPackage());
} }
} }

View File

@@ -461,5 +461,13 @@ public class FileMock implements IFile {
public void setHidden(boolean isHidden) throws CoreException { public void setHidden(boolean isHidden) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden(int options) {
throw new NotImplementedException();
}
public boolean isTeamPrivateMember(int options) {
throw new NotImplementedException();
}
} }

View File

@@ -74,7 +74,8 @@ public final class FolderMock implements IFolder {
// -------- UNIMPLEMENTED METHODS ---------------- // -------- UNIMPLEMENTED METHODS ----------------
public void create(boolean force, boolean local, IProgressMonitor monitor) throws CoreException { public void create(boolean force, boolean local, IProgressMonitor monitor)
throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -106,8 +107,8 @@ public final class FolderMock implements IFolder {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) public void move(IPath destination, boolean force, boolean keepHistory,
throws CoreException { IProgressMonitor monitor) throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -225,7 +226,8 @@ public final class FolderMock implements IFolder {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { public void deleteMarkers(String type, boolean includeSubtypes, int depth)
throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -448,4 +450,11 @@ public final class FolderMock implements IFolder {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden(int options) {
throw new NotImplementedException();
}
public boolean isTeamPrivateMember(int options) {
throw new NotImplementedException();
}
} }

View File

@@ -42,6 +42,14 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
/**
* Mock implementation of {@link IProject}.
* <p/>Supported methods:
* <ul>
* <li>{@link #build(int kind, IProgressMonitor monitor)}</li>
* <li>{@link #members(int kind, String builderName, Map args, IProgressMonitor monitor)}</li>
* </ul>
*/
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class ProjectMock implements IProject { public class ProjectMock implements IProject {
@@ -265,7 +273,8 @@ public class ProjectMock implements IProject {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { public void deleteMarkers(String type, boolean includeSubtypes, int depth)
throws CoreException {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@@ -498,4 +507,11 @@ public class ProjectMock implements IProject {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public boolean isHidden(int options) {
throw new NotImplementedException();
}
public boolean isTeamPrivateMember(int options) {
throw new NotImplementedException();
}
} }

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.AndroidProject.tests">
<!--
This declares that this app uses the instrumentation test runner targeting
the package of com.android.samples. To run the tests use the command:
"adb shell am instrument -w com.android.samples.tests/android.test.InstrumentationTestRunner"
-->
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.AndroidProject"
android:label="Sample test for deployment."/>
<application>
<uses-library android:name="android.test.runner" />
</application>
</manifest>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.testapp">
<application android:icon="@drawable/icon">
<activity android:name="com.android.testapp.MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />"
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<uses-library android:name="android.test.runner"/>
</application>"
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.example.android.apis"
android:label="Tests for Api Demos."/>
</manifest>

View File

@@ -72,6 +72,7 @@
<!-- Create the output directories if they don't exist yet. --> <!-- Create the output directories if they don't exist yet. -->
<target name="dirs"> <target name="dirs">
<echo>Creating output directories if needed...</echo> <echo>Creating output directories if needed...</echo>
<mkdir dir="${resource-folder}" />
<mkdir dir="${external-libs-folder}" /> <mkdir dir="${external-libs-folder}" />
<mkdir dir="${gen-folder}" /> <mkdir dir="${gen-folder}" />
<mkdir dir="${out-folder}" /> <mkdir dir="${out-folder}" />

View File

@@ -36,28 +36,21 @@ from.
__author__ = 'jmatt@google.com (Justin Mattson)' __author__ = 'jmatt@google.com (Justin Mattson)'
from optparse import OptionParser import optparse
import os import os
import stat import stat
import sys import sys
import zipfile import zipfile
from zipfile import ZipFile
import divide_and_compress_constants import divide_and_compress_constants
def Main(argv):
parser = CreateOptionsParser()
(options, args) = parser.parse_args()
VerifyArguments(options, parser)
zipper = DirectoryZipper(options.destination,
options.sourcefiles,
ParseSize(options.filesize),
options.compress)
zipper.StartCompress()
def CreateOptionsParser(): def CreateOptionsParser():
rtn = OptionParser() """Creates the parser for command line arguments.
Returns:
A configured optparse.OptionParser object.
"""
rtn = optparse.OptionParser()
rtn.add_option('-s', '--sourcefiles', dest='sourcefiles', default=None, rtn.add_option('-s', '--sourcefiles', dest='sourcefiles', default=None,
help='The directory containing the files to compress') help='The directory containing the files to compress')
rtn.add_option('-d', '--destination', dest='destination', default=None, rtn.add_option('-d', '--destination', dest='destination', default=None,
@@ -65,9 +58,10 @@ def CreateOptionsParser():
' a child of where the source files exist.')) ' a child of where the source files exist.'))
rtn.add_option('-f', '--filesize', dest='filesize', default='1M', rtn.add_option('-f', '--filesize', dest='filesize', default='1M',
help=('Maximum size of archive files. A number followed by ' help=('Maximum size of archive files. A number followed by '
'a magnitude indicator, eg. 1000000B == one million ' 'a magnitude indicator either "B", "K", "M", or "G". '
'BYTES, 500K == five hundred KILOBYTES, 1.2M == one ' 'Examples:\n 1000000B == one million BYTES\n'
'point two MEGABYTES. 1M == 1048576 BYTES')) ' 1.2M == one point two MEGABYTES\n'
' 1M == 1048576 BYTES'))
rtn.add_option('-n', '--nocompress', action='store_false', dest='compress', rtn.add_option('-n', '--nocompress', action='store_false', dest='compress',
default=True, default=True,
help=('Whether the archive files should be compressed, or ' help=('Whether the archive files should be compressed, or '
@@ -76,49 +70,65 @@ def CreateOptionsParser():
def VerifyArguments(options, parser): def VerifyArguments(options, parser):
"""Runs simple checks on correctness of commandline arguments.
Args:
options: The command line options passed.
parser: The parser object used to parse the command string.
"""
try: try:
if options.sourcefiles is None or options.destination is None: if options.sourcefiles is None or options.destination is None:
parser.print_help() parser.print_help()
sys.exit(-1) sys.exit(-1)
except (AttributeError), err: except AttributeError:
parser.print_help() parser.print_help()
sys.exit(-1) sys.exit(-1)
def ParseSize(size_str): def ParseSize(size_str):
"""Parse the file size argument from a string to a number of bytes.
Args:
size_str: The string representation of the file size.
Returns:
The file size in bytes.
Raises:
ValueError: Raises an error if the numeric or qualifier portions of the
file size argument is invalid.
"""
if len(size_str) < 2: if len(size_str) < 2:
raise ValueError(('filesize argument not understood, please include' raise ValueError(('filesize argument not understood, please include'
' a numeric value and magnitude indicator')) ' a numeric value and magnitude indicator'))
magnitude = size_str[len(size_str)-1:] magnitude = size_str[-1]
if not magnitude in ('K', 'B', 'M'): if not magnitude in ('B', 'K', 'M', 'G'):
raise ValueError(('filesize magnitude indicator not valid, must be \'K\',' raise ValueError(('filesize magnitude indicator not valid, must be "B",'
'\'B\', or \'M\'')) '"K","M", or "G"'))
numeral = float(size_str[0:len(size_str)-1]) numeral = float(size_str[:-1])
if magnitude == 'K': if magnitude == 'K':
numeral *= 1024 numeral *= 1024
elif magnitude == 'M': elif magnitude == 'M':
numeral *= 1048576 numeral *= 1048576
elif magnitude == 'G':
numeral *= 1073741824
return int(numeral) return int(numeral)
class DirectoryZipper(object): class DirectoryZipper(object):
"""Class to compress a directory and all its sub-directories.""" """Class to compress a directory and all its sub-directories."""
current_archive = None
output_dir = None
base_path = None
max_size = None
compress = None
index_fp = None
def __init__(self, output_path, base_dir, archive_size, enable_compression): def __init__(self, output_path, base_dir, archive_size, enable_compression):
"""DirectoryZipper constructor. """DirectoryZipper constructor.
Args: Args:
output_path: the path to write the archives and index file to output_path: A string, the path to write the archives and index file to.
base_dir: the directory to compress base_dir: A string, the directory to compress.
archive_size: the maximum size, in bytes, of a single archive file archive_size: An number, the maximum size, in bytes, of a single
enable_compression: whether or not compression should be enabled, if archive file.
disabled, the files will be written into an uncompresed zip enable_compression: A boolean, whether or not compression should be
enabled, if disabled, the files will be written into an uncompresed
zip.
""" """
self.output_dir = output_path self.output_dir = output_path
self.current_archive = '0.zip' self.current_archive = '0.zip'
@@ -126,6 +136,9 @@ class DirectoryZipper(object):
self.max_size = archive_size self.max_size = archive_size
self.compress = enable_compression self.compress = enable_compression
# Set index_fp to None, because we don't know what it will be yet.
self.index_fp = None
def StartCompress(self): def StartCompress(self):
"""Start compress of the directory. """Start compress of the directory.
@@ -133,7 +146,7 @@ class DirectoryZipper(object):
specified output directory. It will also produce an 'index.txt' file in the specified output directory. It will also produce an 'index.txt' file in the
output directory that maps from file to archive. output directory that maps from file to archive.
""" """
self.index_fp = open(''.join([self.output_dir, 'main.py']), 'w') self.index_fp = open(os.path.join(self.output_dir, 'main.py'), 'w')
self.index_fp.write(divide_and_compress_constants.file_preamble) self.index_fp.write(divide_and_compress_constants.file_preamble)
os.path.walk(self.base_path, self.CompressDirectory, 1) os.path.walk(self.base_path, self.CompressDirectory, 1)
self.index_fp.write(divide_and_compress_constants.file_endpiece) self.index_fp.write(divide_and_compress_constants.file_endpiece)
@@ -149,37 +162,32 @@ class DirectoryZipper(object):
Args: Args:
archive_path: Path to the archive to modify. This archive should not be archive_path: Path to the archive to modify. This archive should not be
open elsewhere, since it will need to be deleted. open elsewhere, since it will need to be deleted.
Return:
A new ZipFile object that points to the modified archive file Returns:
A new ZipFile object that points to the modified archive file.
""" """
if archive_path is None: if archive_path is None:
archive_path = ''.join([self.output_dir, self.current_archive]) archive_path = os.path.join(self.output_dir, self.current_archive)
# Move the old file and create a new one at its old location # Move the old file and create a new one at its old location.
ext_offset = archive_path.rfind('.') root, ext = os.path.splitext(archive_path)
old_archive = ''.join([archive_path[0:ext_offset], '-old', old_archive = ''.join([root, '-old', ext])
archive_path[ext_offset:]])
os.rename(archive_path, old_archive) os.rename(archive_path, old_archive)
old_fp = self.OpenZipFileAtPath(old_archive, mode='r') old_fp = self.OpenZipFileAtPath(old_archive, mode='r')
# By default, store uncompressed.
compress_bit = zipfile.ZIP_STORED
if self.compress: if self.compress:
compress_bit = zipfile.ZIP_DEFLATED
new_fp = self.OpenZipFileAtPath(archive_path, new_fp = self.OpenZipFileAtPath(archive_path,
mode='w', mode='w',
compress=zipfile.ZIP_DEFLATED) compress=compress_bit)
else:
new_fp = self.OpenZipFileAtPath(archive_path,
mode='w',
compress=zipfile.ZIP_STORED)
# Read the old archive in a new archive, except the last one # Read the old archive in a new archive, except the last one.
zip_members = enumerate(old_fp.infolist()) for zip_member in old_fp.infolist()[:-1]:
num_members = len(old_fp.infolist()) new_fp.writestr(zip_member, old_fp.read(zip_member.filename))
while num_members > 1:
this_member = zip_members.next()[1]
new_fp.writestr(this_member.filename, old_fp.read(this_member.filename))
num_members -= 1
# Close files and delete the old one # Close files and delete the old one.
old_fp.close() old_fp.close()
new_fp.close() new_fp.close()
os.unlink(old_archive) os.unlink(old_archive)
@@ -193,11 +201,11 @@ class DirectoryZipper(object):
mode = 'w' mode = 'w'
if mode == 'r': if mode == 'r':
return ZipFile(path, mode) return zipfile.ZipFile(path, mode)
else: else:
return ZipFile(path, mode, compress) return zipfile.ZipFile(path, mode, compress)
def CompressDirectory(self, irrelevant, dir_path, dir_contents): def CompressDirectory(self, unused_id, dir_path, dir_contents):
"""Method to compress the given directory. """Method to compress the given directory.
This method compresses the directory 'dir_path'. It will add to an existing This method compresses the directory 'dir_path'. It will add to an existing
@@ -206,40 +214,35 @@ class DirectoryZipper(object):
mapping of files to archives to the self.index_fp file descriptor mapping of files to archives to the self.index_fp file descriptor
Args: Args:
irrelevant: a numeric identifier passed by the os.path.walk method, this unused_id: A numeric identifier passed by the os.path.walk method, this
is not used by this method is not used by this method.
dir_path: the path to the directory to compress dir_path: A string, the path to the directory to compress.
dir_contents: a list of directory contents to be compressed dir_contents: A list of directory contents to be compressed.
""" """
# Construct the queue of files to be added that this method will use
# construct the queue of files to be added that this method will use
# it seems that dir_contents is given in reverse alphabetical order, # it seems that dir_contents is given in reverse alphabetical order,
# so put them in alphabetical order by inserting to front of the list # so put them in alphabetical order by inserting to front of the list.
dir_contents.sort() dir_contents.sort()
zip_queue = [] zip_queue = []
if dir_path[len(dir_path) - 1:] == os.sep:
for filename in dir_contents: for filename in dir_contents:
zip_queue.append(''.join([dir_path, filename])) zip_queue.append(os.path.join(dir_path, filename))
else:
for filename in dir_contents:
zip_queue.append(''.join([dir_path, os.sep, filename]))
compress_bit = zipfile.ZIP_DEFLATED compress_bit = zipfile.ZIP_DEFLATED
if not self.compress: if not self.compress:
compress_bit = zipfile.ZIP_STORED compress_bit = zipfile.ZIP_STORED
# zip all files in this directory, adding to existing archives and creating # Zip all files in this directory, adding to existing archives and creating
# as necessary # as necessary.
while len(zip_queue) > 0: while zip_queue:
target_file = zip_queue[0] target_file = zip_queue[0]
if os.path.isfile(target_file): if os.path.isfile(target_file):
self.AddFileToArchive(target_file, compress_bit) self.AddFileToArchive(target_file, compress_bit)
# see if adding the new file made our archive too large # See if adding the new file made our archive too large.
if not self.ArchiveIsValid(): if not self.ArchiveIsValid():
# IF fixing fails, the last added file was to large, skip it # IF fixing fails, the last added file was to large, skip it
# ELSE the current archive filled normally, make a new one and try # ELSE the current archive filled normally, make a new one and try
# adding the file again # adding the file again.
if not self.FixArchive('SIZE'): if not self.FixArchive('SIZE'):
zip_queue.pop(0) zip_queue.pop(0)
else: else:
@@ -248,7 +251,7 @@ class DirectoryZipper(object):
0:self.current_archive.rfind('.zip')]) + 1) 0:self.current_archive.rfind('.zip')]) + 1)
else: else:
# if this the first file in the archive, write an index record # Write an index record if necessary.
self.WriteIndexRecord() self.WriteIndexRecord()
zip_queue.pop(0) zip_queue.pop(0)
else: else:
@@ -260,10 +263,10 @@ class DirectoryZipper(object):
Only write an index record if this is the first file to go into archive Only write an index record if this is the first file to go into archive
Returns: Returns:
True if an archive record is written, False if it isn't True if an archive record is written, False if it isn't.
""" """
archive = self.OpenZipFileAtPath( archive = self.OpenZipFileAtPath(
''.join([self.output_dir, self.current_archive]), 'r') os.path.join(self.output_dir, self.current_archive), 'r')
archive_index = archive.infolist() archive_index = archive.infolist()
if len(archive_index) == 1: if len(archive_index) == 1:
self.index_fp.write( self.index_fp.write(
@@ -279,14 +282,14 @@ class DirectoryZipper(object):
"""Make the archive compliant. """Make the archive compliant.
Args: Args:
problem: the reason the archive is invalid problem: An enum, the reason the archive is invalid.
Returns: Returns:
Whether the file(s) removed to fix the archive could conceivably be Whether the file(s) removed to fix the archive could conceivably be
in an archive, but for some reason can't be added to this one. in an archive, but for some reason can't be added to this one.
""" """
archive_path = ''.join([self.output_dir, self.current_archive]) archive_path = os.path.join(self.output_dir, self.current_archive)
rtn_value = None return_value = None
if problem == 'SIZE': if problem == 'SIZE':
archive_obj = self.OpenZipFileAtPath(archive_path, mode='r') archive_obj = self.OpenZipFileAtPath(archive_path, mode='r')
@@ -294,39 +297,41 @@ class DirectoryZipper(object):
# IF there is a single file, that means its too large to compress, # IF there is a single file, that means its too large to compress,
# delete the created archive # delete the created archive
# ELSE do normal finalization # ELSE do normal finalization.
if num_archive_files == 1: if num_archive_files == 1:
print ('WARNING: %s%s is too large to store.' % ( print ('WARNING: %s%s is too large to store.' % (
self.base_path, archive_obj.infolist()[0].filename)) self.base_path, archive_obj.infolist()[0].filename))
archive_obj.close() archive_obj.close()
os.unlink(archive_path) os.unlink(archive_path)
rtn_value = False return_value = False
else: else:
self.RemoveLastFile(''.join([self.output_dir, self.current_archive]))
archive_obj.close() archive_obj.close()
self.RemoveLastFile(
os.path.join(self.output_dir, self.current_archive))
print 'Final archive size for %s is %i' % ( print 'Final archive size for %s is %i' % (
self.current_archive, os.stat(archive_path)[stat.ST_SIZE]) self.current_archive, os.path.getsize(archive_path))
rtn_value = True return_value = True
return rtn_value return return_value
def AddFileToArchive(self, filepath, compress_bit): def AddFileToArchive(self, filepath, compress_bit):
"""Add the file at filepath to the current archive. """Add the file at filepath to the current archive.
Args: Args:
filepath: the path of the file to add filepath: A string, the path of the file to add.
compress_bit: whether or not this fiel should be compressed when added compress_bit: A boolean, whether or not this file should be compressed
when added.
Returns: Returns:
True if the file could be added (typically because this is a file) or True if the file could be added (typically because this is a file) or
False if it couldn't be added (typically because its a directory) False if it couldn't be added (typically because its a directory).
""" """
curr_archive_path = ''.join([self.output_dir, self.current_archive]) curr_archive_path = os.path.join(self.output_dir, self.current_archive)
if os.path.isfile(filepath): if os.path.isfile(filepath) and not os.path.islink(filepath):
if os.stat(filepath)[stat.ST_SIZE] > 1048576: if os.path.getsize(filepath) > 1048576:
print 'Warning: %s is potentially too large to serve on GAE' % filepath print 'Warning: %s is potentially too large to serve on GAE' % filepath
archive = self.OpenZipFileAtPath(curr_archive_path, archive = self.OpenZipFileAtPath(curr_archive_path,
compress=compress_bit) compress=compress_bit)
# add the file to the archive # Add the file to the archive.
archive.write(filepath, filepath[len(self.base_path):]) archive.write(filepath, filepath[len(self.base_path):])
archive.close() archive.close()
return True return True
@@ -340,13 +345,22 @@ class DirectoryZipper(object):
The thought is that eventually this will do additional validation The thought is that eventually this will do additional validation
Returns: Returns:
True if the archive is valid, False if its not True if the archive is valid, False if its not.
""" """
archive_path = ''.join([self.output_dir, self.current_archive]) archive_path = os.path.join(self.output_dir, self.current_archive)
if os.stat(archive_path)[stat.ST_SIZE] > self.max_size: return os.path.getsize(archive_path) <= self.max_size
return False
else:
return True def main(argv):
parser = CreateOptionsParser()
(options, unused_args) = parser.parse_args(args=argv[1:])
VerifyArguments(options, parser)
zipper = DirectoryZipper(options.destination,
options.sourcefiles,
ParseSize(options.filesize),
options.compress)
zipper.StartCompress()
if __name__ == '__main__': if __name__ == '__main__':
Main(sys.argv) main(sys.argv)

View File

@@ -19,42 +19,40 @@
__author__ = 'jmatt@google.com (Justin Mattson)' __author__ = 'jmatt@google.com (Justin Mattson)'
file_preamble = ('#!/usr/bin/env python\n' file_preamble = """#!/usr/bin/env python
'#\n' #
'# Copyright 2008 Google Inc.\n' # Copyright 2008 Google Inc.
'#\n' #
'# Licensed under the Apache License, Version 2.0 (the' # Licensed under the Apache License, Version 2.0 (the "License");
'\"License");\n' # you may not use this file except in compliance with the License.
'# you may not use this file except in compliance with the ' # You may obtain a copy of the License at
'License.\n' #
'# You may obtain a copy of the License at\n' # http://www.apache.org/licenses/LICENSE-2.0
'#\n' #
'# http://www.apache.org/licenses/LICENSE-2.0\n' # Unless required by applicable law or agreed to in writing, software
'#\n' # distributed under the License is distributed on an \"AS IS\" BASIS,
'# Unless required by applicable law or agreed to in writing,' # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' software\n' # See the License for the specific language governing permissions and
'# distributed under the License is distributed on an \"AS' # limitations under the License.
'IS\" BASIS,\n' #
'# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either '
'express or implied.\n'
'# See the License for the specific language governing'
' permissions and\n'
'# limitations under the License.\n'
'#\n\n'
'import wsgiref.handlers\n'
'from google.appengine.ext import zipserve\n'
'from google.appengine.ext import webapp\n'
'import memcache_zipserve\n\n\n'
'class MainHandler(webapp.RequestHandler):\n\n'
' def get(self):\n'
' self.response.out.write(\'Hello world!\')\n\n'
'def main():\n'
' application = webapp.WSGIApplication([(\'/(.*)\','
' memcache_zipserve.create_handler([')
file_endpiece = ('])),\n' import wsgiref.handlers\n'
'],\n' from google.appengine.ext import zipserve\n'
'debug=False)\n' from google.appengine.ext import webapp\n'
' wsgiref.handlers.CGIHandler().run(application)\n\n' import memcache_zipserve\n\n\n'
'if __name__ == \'__main__\':\n' class MainHandler(webapp.RequestHandler):
' main()')
def get(self):
self.response.out.write('Hello world!')
def main():
application = webapp.WSGIApplication(['/(.*)',
memcache_zipserve.create_handler(["""
file_endpiece = """])),
],
debug=False)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == __main__:
main()"""

View File

@@ -17,7 +17,7 @@
"""Tests for divide_and_compress.py. """Tests for divide_and_compress.py.
TODO: Add tests for module methods. TODO(jmatt): Add tests for module methods.
""" """
__author__ = 'jmatt@google.com (Justin Mattson)' __author__ = 'jmatt@google.com (Justin Mattson)'
@@ -26,10 +26,9 @@ import os
import stat import stat
import unittest import unittest
import zipfile import zipfile
from zipfile import ZipFile
import divide_and_compress import divide_and_compress
from mox import mox import mox
class BagOfParts(object): class BagOfParts(object):
@@ -58,6 +57,10 @@ class ValidAndRemoveTests(unittest.TestCase):
'sdjfljkgsc n;iself') 'sdjfljkgsc n;iself')
self.files = {'file1': file1, 'file2': file2} self.files = {'file1': file1, 'file2': file2}
def tearDown(self):
"""Remove any stubs we've created."""
self.my_mox.UnsetStubs()
def testArchiveIsValid(self): def testArchiveIsValid(self):
"""Test the DirectoryZipper.ArchiveIsValid method. """Test the DirectoryZipper.ArchiveIsValid method.
@@ -119,7 +122,7 @@ class ValidAndRemoveTests(unittest.TestCase):
A configured mocked A configured mocked
""" """
source_zip = self.my_mox.CreateMock(ZipFile) source_zip = self.my_mox.CreateMock(zipfile.ZipFile)
source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']]) source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']]) source_zip.infolist().AndReturn([self.files['file1'], self.files['file1']])
source_zip.read(self.files['file1'].filename).AndReturn( source_zip.read(self.files['file1'].filename).AndReturn(
@@ -137,16 +140,12 @@ class ValidAndRemoveTests(unittest.TestCase):
A configured mocked A configured mocked
""" """
dest_zip = mox.MockObject(ZipFile) dest_zip = mox.MockObject(zipfile.ZipFile)
dest_zip.writestr(self.files['file1'].filename, dest_zip.writestr(self.files['file1'].filename,
self.files['file1'].contents) self.files['file1'].contents)
dest_zip.close() dest_zip.close()
return dest_zip return dest_zip
def tearDown(self):
"""Remove any stubs we've created."""
self.my_mox.UnsetStubs()
class FixArchiveTests(unittest.TestCase): class FixArchiveTests(unittest.TestCase):
"""Tests for the DirectoryZipper.FixArchive method.""" """Tests for the DirectoryZipper.FixArchive method."""
@@ -158,6 +157,10 @@ class FixArchiveTests(unittest.TestCase):
self.file1.filename = 'file1.txt' self.file1.filename = 'file1.txt'
self.file1.contents = 'This is a test file' self.file1.contents = 'This is a test file'
def tearDown(self):
"""Unset any mocks that we've created."""
self.my_mox.UnsetStubs()
def _InitMultiFileData(self): def _InitMultiFileData(self):
"""Create an array of mock file objects. """Create an array of mock file objects.
@@ -211,7 +214,7 @@ class FixArchiveTests(unittest.TestCase):
Returns: Returns:
A configured mock object A configured mock object
""" """
mock_zip = self.my_mox.CreateMock(ZipFile) mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
mock_zip.infolist().AndReturn([self.file1]) mock_zip.infolist().AndReturn([self.file1])
mock_zip.infolist().AndReturn([self.file1]) mock_zip.infolist().AndReturn([self.file1])
mock_zip.close() mock_zip.close()
@@ -250,15 +253,11 @@ class FixArchiveTests(unittest.TestCase):
A configured mock object A configured mock object
""" """
self._InitMultiFileData() self._InitMultiFileData()
mock_zip = self.my_mox.CreateMock(ZipFile) mock_zip = self.my_mox.CreateMock(zipfile.ZipFile)
mock_zip.infolist().AndReturn(self.multi_file_dir) mock_zip.infolist().AndReturn(self.multi_file_dir)
mock_zip.close() mock_zip.close()
return mock_zip return mock_zip
def tearDown(self):
"""Unset any mocks that we've created."""
self.my_mox.UnsetStubs()
class AddFileToArchiveTest(unittest.TestCase): class AddFileToArchiveTest(unittest.TestCase):
"""Test behavior of method to add a file to an archive.""" """Test behavior of method to add a file to an archive."""
@@ -270,6 +269,9 @@ class AddFileToArchiveTest(unittest.TestCase):
self.file_to_add = 'file.txt' self.file_to_add = 'file.txt'
self.input_dir = '/foo/bar/baz/' self.input_dir = '/foo/bar/baz/'
def tearDown(self):
self.my_mox.UnsetStubs()
def testAddFileToArchive(self): def testAddFileToArchive(self):
"""Test the DirectoryZipper.AddFileToArchive method. """Test the DirectoryZipper.AddFileToArchive method.
@@ -312,15 +314,12 @@ class AddFileToArchiveTest(unittest.TestCase):
Returns: Returns:
A configured mock object A configured mock object
""" """
archive_mock = self.my_mox.CreateMock(ZipFile) archive_mock = self.my_mox.CreateMock(zipfile.ZipFile)
archive_mock.write(''.join([self.input_dir, self.file_to_add]), archive_mock.write(''.join([self.input_dir, self.file_to_add]),
self.file_to_add) self.file_to_add)
archive_mock.close() archive_mock.close()
return archive_mock return archive_mock
def tearDown(self):
self.my_mox.UnsetStubs()
class CompressDirectoryTest(unittest.TestCase): class CompressDirectoryTest(unittest.TestCase):
"""Test the master method of the class. """Test the master method of the class.

View File

@@ -35,6 +35,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern;
/** /**
* Main class for the 'android' application. * Main class for the 'android' application.
@@ -50,6 +51,12 @@ class Main {
private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" }; private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" }; private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
/** Regex used to validate characters that compose an AVD name. */
private final static Pattern RE_AVD_NAME = Pattern.compile("[a-zA-Z0-9._-]+");
/** List of valid characters for an AVD name. Used for display purposes. */
private final static String CHARS_AVD_NAME = "a-z A-Z 0-9 . _ -";
/** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */ /** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */
private String mSdkFolder; private String mSdkFolder;
/** Logger object. Use this to print normal output, warnings or errors. */ /** Logger object. Use this to print normal output, warnings or errors. */
@@ -240,10 +247,40 @@ class Main {
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath()); String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
String projectName = mSdkCommandLine.getParamName();
String packageName = mSdkCommandLine.getParamProjectPackage();
String activityName = mSdkCommandLine.getParamProjectActivity();
if (projectName != null &&
!ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) {
errorAndExit(
"Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
projectName, ProjectCreator.CHARS_PROJECT_NAME);
return;
}
if (activityName != null &&
!ProjectCreator.RE_ACTIVITY_NAME.matcher(activityName).matches()) {
errorAndExit(
"Activity name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
activityName, ProjectCreator.CHARS_ACTIVITY_NAME);
return;
}
if (packageName != null &&
!ProjectCreator.RE_PACKAGE_NAME.matcher(packageName).matches()) {
errorAndExit(
"Package name '%1$s' contains invalid characters.\n" +
"A package name must be constitued of two Java identifiers.\n" +
"Each identifier allowed characters are: %2$s",
packageName, ProjectCreator.CHARS_PACKAGE_NAME);
return;
}
creator.createProject(projectDir, creator.createProject(projectDir,
mSdkCommandLine.getParamName(), projectName,
mSdkCommandLine.getParamProjectPackage(), activityName,
mSdkCommandLine.getParamProjectActivity(), packageName,
target, target,
false /* isTestProject*/); false /* isTestProject*/);
} }
@@ -447,6 +484,14 @@ class Main {
AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog); AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
String avdName = mSdkCommandLine.getParamName(); String avdName = mSdkCommandLine.getParamName();
if (!RE_AVD_NAME.matcher(avdName).matches()) {
errorAndExit(
"AVD name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
avdName, CHARS_AVD_NAME);
return;
}
AvdInfo info = avdManager.getAvd(avdName); AvdInfo info = avdManager.getAvd(avdName);
if (info != null) { if (info != null) {
if (mSdkCommandLine.getFlagForce()) { if (mSdkCommandLine.getFlagForce()) {

View File

@@ -28,7 +28,7 @@ final class PlatformTarget implements IAndroidTarget {
/** String used to get a hash to the platform target */ /** String used to get a hash to the platform target */
private final static String PLATFORM_HASH = "android-%d"; private final static String PLATFORM_HASH = "android-%d";
private final static String PLATFORM_VENDOR = "Android"; private final static String PLATFORM_VENDOR = "Android Open Source Project";
private final static String PLATFORM_NAME = "Android %s"; private final static String PLATFORM_NAME = "Android %s";
private final String mLocation; private final String mLocation;

View File

@@ -177,7 +177,7 @@ public final class AvdManager {
public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException { public AvdManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
mSdk = sdk; mSdk = sdk;
mSdkLog = sdkLog; mSdkLog = sdkLog;
buildAvdList(); buildAvdList(mAvdList);
} }
/** /**
@@ -202,6 +202,20 @@ public final class AvdManager {
return null; 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<AvdInfo> list = new ArrayList<AvdInfo>();
buildAvdList(list);
mAvdList.clear();
mAvdList.addAll(list);
}
/** /**
* Creates a new AVD. It is expected that there is no existing AVD with this name already. * Creates a new AVD. It is expected that there is no existing AVD with this name already.
* @param avdFolder the data folder for the AVD. It will be created as needed. * @param avdFolder the data folder for the AVD. It will be created as needed.
@@ -620,7 +634,7 @@ public final class AvdManager {
} }
} }
private void buildAvdList() throws AndroidLocationException { private void buildAvdList(ArrayList<AvdInfo> list) throws AndroidLocationException {
// get the Android prefs location. // get the Android prefs location.
String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD; String avdRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD;
@@ -664,7 +678,7 @@ public final class AvdManager {
for (File avd : avds) { for (File avd : avds) {
AvdInfo info = parseAvdInfo(avd); AvdInfo info = parseAvdInfo(avd);
if (info != null) { if (info != null) {
mAvdList.add(info); list.add(info);
if (avdListDebug) { if (avdListDebug) {
mSdkLog.printf("[AVD LIST DEBUG] Added AVD '%s'\n", info.getPath()); mSdkLog.printf("[AVD LIST DEBUG] Added AVD '%s'\n", info.getPath());
} }

View File

@@ -62,6 +62,27 @@ public class ProjectCreator {
private final static String FOLDER_TESTS = "tests"; private final static String FOLDER_TESTS = "tests";
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
public static final Pattern RE_PROJECT_NAME = Pattern.compile("[a-zA-Z0-9_]+");
/** List of valid characters for a project name. Used for display purposes. */
public final static String CHARS_PROJECT_NAME = "a-z A-Z 0-9 _";
/** Pattern for characters accepted in a package name. A package is list of Java identifier
* separated by a dot. We need to have at least one dot (e.g. a two-level package name).
* A Java identifier cannot start by a digit. */
public static final Pattern RE_PACKAGE_NAME =
Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*(?:\\.[a-zA-Z_][a-zA-Z0-9_]*)+");
/** List of valid characters for a project name. Used for display purposes. */
public final static String CHARS_PACKAGE_NAME = "a-z A-Z 0-9 _";
/** Pattern for characters accepted in an activity name, which is a Java identifier. */
public static final Pattern RE_ACTIVITY_NAME =
Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
/** List of valid characters for a project name. Used for display purposes. */
public final static String CHARS_ACTIVITY_NAME = "a-z A-Z 0-9 _";
public enum OutputLevel { public enum OutputLevel {
/** Silent mode. Project creation will only display errors. */ /** Silent mode. Project creation will only display errors. */
SILENT, SILENT,
@@ -106,11 +127,17 @@ public class ProjectCreator {
/** /**
* Creates a new project. * Creates a new project.
* <p/>
* The caller should have already checked and sanitized the parameters.
* *
* @param folderPath the folder of the project to create. * @param folderPath the folder of the project to create.
* @param projectName the name of the project. * @param projectName the name of the project. The name must match the
* @param packageName the package of the project. * {@link #RE_PROJECT_NAME} regex.
* @param activityName the activity of the project as it will appear in the manifest. * @param packageName the package of the project. The name must match the
* {@link #RE_PACKAGE_NAME} regex.
* @param activityName the activity of the project as it will appear in the manifest. Can be
* null if no activity should be created. The name must match the
* {@link #RE_ACTIVITY_NAME} regex.
* @param target the project target. * @param target the project target.
* @param isTestProject whether the project to create is a test project. * @param isTestProject whether the project to create is a test project.
*/ */