Merge commit 'korg/cupcake'
This commit is contained in:
190
apps/CustomLocale/NOTICE
Normal file
190
apps/CustomLocale/NOTICE
Normal 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
190
apps/Development/NOTICE
Normal 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
190
apps/SdkSetup/NOTICE
Normal 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
190
apps/SpareParts/NOTICE
Normal 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
|
||||||
|
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
190
apps/launchperf/NOTICE
Normal 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
|
||||||
|
|
||||||
@@ -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) {
|
||||||
i++;
|
// We don't want to count throttling as an event.
|
||||||
|
if (!(ev instanceof MonkeyThrottleEvent)) {
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
29
emulator/sensors/Android.mk
Normal file
29
emulator/sensors/Android.mk
Normal 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
|
||||||
591
emulator/sensors/sensors_qemu.c
Normal file
591
emulator/sensors/sensors_qemu.c
Normal 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
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 -->
|
||||||
<!-- ************************************* -->
|
<!-- ************************************* -->
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
19
testrunner/Android.mk
Normal 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)
|
||||||
45
testrunner/android_build.py
Normal file
45
testrunner/android_build.py
Normal 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
|
||||||
@@ -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
280
testrunner/runtest.py
Executable 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()
|
||||||
@@ -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"
|
||||||
@@ -169,12 +180,19 @@ These attributes map to the following commands:
|
|||||||
runner=".MediaFrameworkTestRunner"
|
runner=".MediaFrameworkTestRunner"
|
||||||
coverage_target="framework"
|
coverage_target="framework"
|
||||||
continuous="true" />
|
continuous="true" />
|
||||||
|
|
||||||
<test name="mediaunit"
|
<test name="mediaunit"
|
||||||
build_path="frameworks/base/media/tests/MediaFrameworkTest"
|
build_path="frameworks/base/media/tests/MediaFrameworkTest"
|
||||||
package="com.android.mediaframeworktest"
|
package="com.android.mediaframeworktest"
|
||||||
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"
|
||||||
|
|||||||
@@ -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.
|
||||||
task.createArg().setValue("-S");
|
File res = new File(mResources);
|
||||||
task.createArg().setValue(mResources);
|
if (res.isDirectory()) {
|
||||||
|
task.createArg().setValue("-S");
|
||||||
|
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");
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +265,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
|||||||
int endKeyPos = line.indexOf('=', keyStartPos);
|
int endKeyPos = line.indexOf('=', keyStartPos);
|
||||||
if (endKeyPos != -1) {
|
if (endKeyPos != -1) {
|
||||||
mCurrentKey = line.substring(keyStartPos, endKeyPos).trim();
|
mCurrentKey = line.substring(keyStartPos, endKeyPos).trim();
|
||||||
parseValue(line, endKeyPos+1);
|
parseValue(line, endKeyPos + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
@@ -351,13 +385,19 @@ public class InstrumentationResultParser extends MultiLineReceiver {
|
|||||||
String timeString = line.substring(startPos);
|
String timeString = line.substring(startPos);
|
||||||
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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,27 +21,35 @@ 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.
|
||||||
*
|
*
|
||||||
@@ -56,12 +64,10 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alternate constructor. Uses default instrumentation runner.
|
* Alternate constructor. Uses default instrumentation runner.
|
||||||
*
|
*
|
||||||
@@ -72,7 +78,7 @@ public class RemoteAndroidTestRunner {
|
|||||||
IDevice remoteDevice) {
|
IDevice remoteDevice) {
|
||||||
this(packageName, null, remoteDevice);
|
this(packageName, null, remoteDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the application package name.
|
* Returns the application package name.
|
||||||
*/
|
*/
|
||||||
@@ -89,14 +95,14 @@ public class RemoteAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
return mRunnerName;
|
return mRunnerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the complete instrumentation component path.
|
* Returns the complete instrumentation component path.
|
||||||
*/
|
*/
|
||||||
private String getRunnerPath() {
|
private String getRunnerPath() {
|
||||||
return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
|
return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets to run only tests in this class
|
* Sets to run only tests in this class
|
||||||
* Must be called before 'run'.
|
* Must be called before 'run'.
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,15 +125,15 @@ public class RemoteAndroidTestRunner {
|
|||||||
public void setClassNames(String[] classNames) {
|
public void setClassNames(String[] classNames) {
|
||||||
StringBuilder classArgBuilder = new StringBuilder();
|
StringBuilder classArgBuilder = new StringBuilder();
|
||||||
|
|
||||||
for (int i=0; i < classNames.length; i++) {
|
for (int i = 0; i < classNames.length; i++) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
classArgBuilder.append(CLASS_SEPARATOR);
|
classArgBuilder.append(CLASS_SEPARATOR);
|
||||||
}
|
}
|
||||||
classArgBuilder.append(classNames[i]);
|
classArgBuilder.append(classNames[i]);
|
||||||
}
|
}
|
||||||
mClassArg = classArgBuilder.toString();
|
setClassName(classArgBuilder.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets to run only specified test method
|
* Sets to run only specified test method
|
||||||
* Must be called before 'run'.
|
* Must be called before 'run'.
|
||||||
@@ -136,47 +142,70 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute this test run.
|
* Execute this test run.
|
||||||
*
|
*
|
||||||
* @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);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser);
|
mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -184,7 +213,7 @@ public class RemoteAndroidTestRunner {
|
|||||||
listener.testRunFailed(e.toString());
|
listener.testRunFailed(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests cancellation of this test run.
|
* Requests cancellation of this test run.
|
||||||
*/
|
*/
|
||||||
@@ -193,36 +222,19 @@ public class RemoteAndroidTestRunner {
|
|||||||
mParser.cancel();
|
mParser.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the test class argument.
|
|
||||||
*/
|
|
||||||
private String getClassArg() {
|
|
||||||
return mClassArg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the full instrumentation command which specifies the test classes to execute.
|
|
||||||
* 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
|
* Returns the full instrumentation command line syntax for the provided instrumentation
|
||||||
* empty string.
|
* arguments.
|
||||||
|
* Returns an empty string if no arguments were specified.
|
||||||
*/
|
*/
|
||||||
private String getLogCmd() {
|
private String getArgsCommand() {
|
||||||
if (mLogOnlyMode) {
|
StringBuilder commandBuilder = new StringBuilder();
|
||||||
return "-e log true";
|
for (Entry<String, String> argPair : mArgMap.entrySet()) {
|
||||||
}
|
final String argCmd = String.format(" -e %s %s", argPair.getKey(),
|
||||||
else {
|
argPair.getValue());
|
||||||
return "";
|
commandBuilder.append(argCmd);
|
||||||
}
|
}
|
||||||
|
return commandBuilder.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,9 +103,43 @@ public class InstrumentationResultParserTest extends TestCase {
|
|||||||
injectTestString(timeString);
|
injectTestString(timeString);
|
||||||
assertEquals(4900, mTestResult.mTestTime);
|
assertEquals(4900, mTestResult.mTestTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 |
@@ -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>
|
||||||
|
|||||||
@@ -1040,6 +1040,16 @@ public class AdtPlugin extends AbstractUIPlugin {
|
|||||||
mSdkIsLoaded = LoadStatus.LOADED;
|
mSdkIsLoaded = LoadStatus.LOADED;
|
||||||
|
|
||||||
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
|
||||||
@@ -1047,14 +1057,13 @@ public class AdtPlugin extends AbstractUIPlugin {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,19 +306,34 @@ 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 (javaPackage == null || javaPackage.length() == 0) {
|
if (minSdkVersion != AndroidManifestParser.INVALID_MIN_SDK &&
|
||||||
// looks like the AndroidManifest file isn't valid.
|
minSdkVersion < projectTarget.getApiVersionNumber()) {
|
||||||
String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
|
// check it against the target api level
|
||||||
AndroidConstants.FN_ANDROID_MANIFEST);
|
String msg = String.format(
|
||||||
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
|
"Manifest min SDK version (%1$d) is lower than project target API level (%2$d)",
|
||||||
msg);
|
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.
|
// This interrupts the build. The next builders will not run.
|
||||||
stopBuild(msg);
|
stopBuild(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (javaPackage == null || javaPackage.length() == 0) {
|
||||||
|
// looks like the AndroidManifest file isn't valid.
|
||||||
|
String msg = String.format(Messages.s_Doesnt_Declare_Package_Error,
|
||||||
|
AndroidConstants.FN_ANDROID_MANIFEST);
|
||||||
|
AdtPlugin.printErrorToConsole(project, msg);
|
||||||
|
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
|
||||||
|
|
||||||
|
// This interrupts the build. The next builders will not run.
|
||||||
|
stopBuild(msg);
|
||||||
|
}
|
||||||
|
|
||||||
// at this point we have the java package. We need to make sure it's not a different
|
// at this point we have the java package. We need to make sure it's not a different
|
||||||
// package than the previous one that were built.
|
// package than the previous one that were built.
|
||||||
if (javaPackage.equals(mManifestPackage) == false) {
|
if (javaPackage.equals(mManifestPackage) == false) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
loadActivities(proj);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the launch action.
|
// load the launch action.
|
||||||
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
|
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -263,6 +266,17 @@ public class NewProjectCreationPage extends WizardPage {
|
|||||||
// Validate. This will complain about the first empty field.
|
// Validate. This will complain about the first empty field.
|
||||||
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:
|
||||||
@@ -389,18 +403,35 @@ public class NewProjectCreationPage extends WizardPage {
|
|||||||
group.setFont(parent.getFont());
|
group.setFont(parent.getFont());
|
||||||
group.setText("Target");
|
group.setText("Target");
|
||||||
|
|
||||||
// get the targets from the sdk
|
// The selector is created without targets. They are added below in the change listener.
|
||||||
IAndroidTarget[] targets = null;
|
mSdkTargetSelector = new SdkTargetSelector(group, null, false /*multi-selection*/);
|
||||||
if (Sdk.getCurrent() != null) {
|
|
||||||
targets = Sdk.getCurrent().getTargets();
|
|
||||||
}
|
|
||||||
|
|
||||||
mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/);
|
mSdkTargetChangeListener = new ITargetChangeListener() {
|
||||||
|
public void onProjectTargetChange(IProject changedProject) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
// If there's only one target, select it
|
public void onTargetsLoaded() {
|
||||||
if (targets != null && targets.length == 1) {
|
// Update the sdk target selector with the new targets
|
||||||
mSdkTargetSelector.setSelection(targets[0]);
|
|
||||||
}
|
// get the targets from the sdk
|
||||||
|
IAndroidTarget[] targets = null;
|
||||||
|
if (Sdk.getCurrent() != null) {
|
||||||
|
targets = Sdk.getCurrent().getTargets();
|
||||||
|
}
|
||||||
|
mSdkTargetSelector.setTargets(targets);
|
||||||
|
|
||||||
|
// If there's only one target, select it
|
||||||
|
if (targets != null && targets.length == 1) {
|
||||||
|
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());
|
||||||
|
|||||||
@@ -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$
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -251,12 +257,8 @@ public class AndroidManifestParser {
|
|||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
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:
|
||||||
@@ -445,6 +447,25 @@ public class AndroidManifestParser {
|
|||||||
addProcessName(processName);
|
addProcessName(processName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the instrumentation nodes.
|
||||||
|
* @param attributes the attributes for the activity node.
|
||||||
|
* node is representing
|
||||||
|
*/
|
||||||
|
private void processInstrumentationNode(Attributes attributes) {
|
||||||
|
// lets get the class name, and check it if required.
|
||||||
|
String instrumentationName = getAttributeValue(attributes, ATTRIBUTE_NAME,
|
||||||
|
true /* hasNamespace */);
|
||||||
|
if (instrumentationName != null) {
|
||||||
|
String instrClassName = combinePackageAndClassName(mPackage, instrumentationName);
|
||||||
|
mInstrumentations.add(instrClassName);
|
||||||
|
if (mMarkErrors) {
|
||||||
|
checkClass(instrClassName, AndroidConstants.CLASS_INSTRUMENTATION,
|
||||||
|
true /* testVisibility */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks that a class is valid and can be used in the Android Manifest.
|
* Checks that a class is valid and can be used in the Android Manifest.
|
||||||
@@ -480,8 +501,7 @@ public class AndroidManifestParser {
|
|||||||
} catch (CoreException e) {
|
} catch (CoreException e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -561,25 +581,41 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the Android Manifest, and returns an object containing the result of the parsing.
|
* Parses the Android Manifest, and returns an object containing the result of the parsing.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -610,16 +646,33 @@ 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,10 +715,15 @@ 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;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -82,7 +81,7 @@ public class ProjectChooserHelper {
|
|||||||
|
|
||||||
// open the dialog and return the object selected if OK was clicked, or null otherwise
|
// open the dialog and return the object selected if OK was clicked, or null otherwise
|
||||||
if (dialog.open() == Window.OK) {
|
if (dialog.open() == Window.OK) {
|
||||||
return (IJavaProject)dialog.getFirstResult();
|
return (IJavaProject) dialog.getFirstResult();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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/>
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -975,18 +977,14 @@ public class GraphicalLayoutEditor extends AbstractGraphicalLayoutEditor
|
|||||||
int themeIndex = mThemeCombo.getSelectionIndex();
|
int themeIndex = mThemeCombo.getSelectionIndex();
|
||||||
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,45 +1829,16 @@ 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;
|
boolean isProjectTheme = themeIndex >= mPlatformThemeCount;
|
||||||
if (bridge.apiLevel >= 3) {
|
|
||||||
// call the new api with proper theme differentiator and
|
|
||||||
// density/dpi support.
|
|
||||||
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) {
|
||||||
model.setEditData(result.getImage());
|
model.setEditData(result.getImage());
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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];
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1233,8 +1233,9 @@ public class UiElementNode implements IPropertySource {
|
|||||||
Node attr = attrs.item(n);
|
Node attr = attrs.item(n);
|
||||||
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++) {
|
||||||
@@ -1475,18 +1477,18 @@ 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 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +340,43 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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$
|
||||||
mOsRootDataPath = url.getFile();
|
|
||||||
|
if (Platform.isRunning()) {
|
||||||
|
sLogger.info("Running as an Eclipse Plug-in JUnit test, using FileLocator");
|
||||||
|
try {
|
||||||
|
mOsRootDataPath = FileLocator.resolve(url).getFile();
|
||||||
|
} catch (IOException e) {
|
||||||
|
sLogger.warning("IOException while using FileLocator, reverting to url");
|
||||||
|
mOsRootDataPath = url.getFile();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sLogger.info("Running as an plain JUnit test, using url as-is");
|
||||||
|
mOsRootDataPath = url.getFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mOsRootDataPath.equals(AndroidConstants.WS_SEP + "data")) {
|
||||||
|
sLogger.warning("Resource data not found using class loader!, Defaulting to no path");
|
||||||
|
}
|
||||||
|
|
||||||
if (!mOsRootDataPath.endsWith(File.separator)) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,17 +16,20 @@
|
|||||||
|
|
||||||
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 PACKAGE_NAME = "com.android.testapp"; //$NON-NLS-1$
|
private static final String INSTRUMENTATION_XML = "AndroidManifest-instrumentation.xml"; //$NON-NLS-1$
|
||||||
|
private static final String TESTAPP_XML = "AndroidManifest-testapp.xml"; //$NON-NLS-1$
|
||||||
|
private static final String PACKAGE_NAME = "com.android.testapp"; //$NON-NLS-1$
|
||||||
private static final String ACTIVITY_NAME = "com.android.testapp.MainActivity"; //$NON-NLS-1$
|
private static final String 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$
|
||||||
private static final String INSTRUMENTATION_NAME = "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
|
private static final String INSTRUMENTATION_NAME = "android.test.InstrumentationTestRunner"; //$NON-NLS-1$
|
||||||
@@ -35,60 +38,46 @@ public class AndroidManifestParserTest extends TestCase {
|
|||||||
protected void setUp() throws Exception {
|
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());
|
|
||||||
|
|
||||||
mManifest = AndroidManifestParser.parseForData(mockFile);
|
testFilePath = AdtTestData.getInstance().getTestFilePath(
|
||||||
assertNotNull(mManifest);
|
INSTRUMENTATION_XML);
|
||||||
|
mManifestInstrumentation = AndroidManifestParser.parseForData(testFilePath);
|
||||||
|
assertNotNull(mManifestInstrumentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetInstrumentationInformation() {
|
||||||
|
assertEquals(1, mManifestInstrumentation.getInstrumentations().length);
|
||||||
|
assertEquals(INSTRUMENTATION_NAME, mManifestTestApp.getInstrumentations()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
public void testGetPackage() {
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,24 +430,31 @@ public final class FolderMock implements IFolder {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<?,?> getPersistentProperties() throws CoreException {
|
public Map<?,?> getPersistentProperties() throws CoreException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<?,?> getSessionProperties() throws CoreException {
|
public Map<?,?> getSessionProperties() throws CoreException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDerived(int options) {
|
public boolean isDerived(int options) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHidden() {
|
public boolean isHidden() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,29 +482,36 @@ public class ProjectMock implements IProject {
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void create(IProjectDescription description, int updateFlags,
|
public void create(IProjectDescription description, int updateFlags,
|
||||||
IProgressMonitor monitor) throws CoreException {
|
IProgressMonitor monitor) throws CoreException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<?,?> getPersistentProperties() throws CoreException {
|
public Map<?,?> getPersistentProperties() throws CoreException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<?,?> getSessionProperties() throws CoreException {
|
public Map<?,?> getSessionProperties() throws CoreException {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDerived(int options) {
|
public boolean isDerived(int options) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHidden() {
|
public boolean isHidden() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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}" />
|
||||||
|
|||||||
@@ -36,89 +36,99 @@ 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,
|
||||||
help=('Where to put the archive files, this should not be'
|
help=('Where to put the archive files, this should not be'
|
||||||
' 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 '
|
||||||
'just a concatenation of the source files'))
|
'just a concatenation of the source files'))
|
||||||
return rtn
|
return rtn
|
||||||
|
|
||||||
|
|
||||||
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:
|
||||||
new_fp = self.OpenZipFileAtPath(archive_path,
|
compress_bit = zipfile.ZIP_DEFLATED
|
||||||
mode='w',
|
new_fp = self.OpenZipFileAtPath(archive_path,
|
||||||
compress=zipfile.ZIP_DEFLATED)
|
mode='w',
|
||||||
else:
|
compress=compress_bit)
|
||||||
new_fp = self.OpenZipFileAtPath(archive_path,
|
|
||||||
mode='w',
|
|
||||||
compress=zipfile.ZIP_STORED)
|
|
||||||
|
|
||||||
# Read the old archive in a new archive, except the last one
|
|
||||||
zip_members = enumerate(old_fp.infolist())
|
|
||||||
num_members = len(old_fp.infolist())
|
|
||||||
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
|
# Read the old archive in a new archive, except the last one.
|
||||||
|
for zip_member in old_fp.infolist()[:-1]:
|
||||||
|
new_fp.writestr(zip_member, old_fp.read(zip_member.filename))
|
||||||
|
|
||||||
|
# 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(os.path.join(dir_path, filename))
|
||||||
zip_queue.append(''.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,54 +282,56 @@ 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')
|
||||||
num_archive_files = len(archive_obj.infolist())
|
num_archive_files = len(archive_obj.infolist())
|
||||||
|
|
||||||
# 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)
|
||||||
|
|||||||
@@ -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()"""
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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. */
|
||||||
@@ -239,11 +246,41 @@ class Main {
|
|||||||
mSdkLog);
|
mSdkLog);
|
||||||
|
|
||||||
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()) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,6 +201,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.
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user