├── CONTRIB.md ├── LICENSE ├── README.md ├── mobile-translate ├── Code.gs ├── README.md └── app │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── google │ │ └── samples │ │ └── mobiledoctranslate │ │ ├── DefaultLaunchActivity.java │ │ └── MainActivity.java │ └── res │ ├── layout-land │ └── activity_main.xml │ ├── layout │ ├── activity_main.xml │ ├── buttons.xml │ ├── input_text_pane.xml │ ├── launch_default.xml │ └── output_text_pane.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── packaging.yaml /CONTRIB.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA] 13 | (https://developers.google.com/open-source/cla/individual). 14 | * If you work for a company that wants to allow you to contribute your work, 15 | then you'll need to sign a [corporate CLA] 16 | (https://developers.google.com/open-source/cla/corporate). 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Google Cloud Platform Samples Style Guide] 32 | (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 35 | 1. Submit a pull request. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | -------------- 3 | 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "{}" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright {yyyy} {name of copyright owner} 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | All image and audio files (including *.png, *.jpg, *.svg, *.mp3, *.wav 206 | and *.ogg) are licensed under the CC-BY-NC license. All other files are 207 | licensed under the Apache 2 license. 208 | 209 | CC-BY-NC License 210 | ---------------- 211 | 212 | Attribution-NonCommercial-ShareAlike 4.0 International 213 | 214 | ======================================================================= 215 | 216 | Creative Commons Corporation ("Creative Commons") is not a law firm and 217 | does not provide legal services or legal advice. Distribution of 218 | Creative Commons public licenses does not create a lawyer-client or 219 | other relationship. Creative Commons makes its licenses and related 220 | information available on an "as-is" basis. Creative Commons gives no 221 | warranties regarding its licenses, any material licensed under their 222 | terms and conditions, or any related information. Creative Commons 223 | disclaims all liability for damages resulting from their use to the 224 | fullest extent possible. 225 | 226 | Using Creative Commons Public Licenses 227 | 228 | Creative Commons public licenses provide a standard set of terms and 229 | conditions that creators and other rights holders may use to share 230 | original works of authorship and other material subject to copyright 231 | and certain other rights specified in the public license below. The 232 | following considerations are for informational purposes only, are not 233 | exhaustive, and do not form part of our licenses. 234 | 235 | Considerations for licensors: Our public licenses are 236 | intended for use by those authorized to give the public 237 | permission to use material in ways otherwise restricted by 238 | copyright and certain other rights. Our licenses are 239 | irrevocable. Licensors should read and understand the terms 240 | and conditions of the license they choose before applying it. 241 | Licensors should also secure all rights necessary before 242 | applying our licenses so that the public can reuse the 243 | material as expected. Licensors should clearly mark any 244 | material not subject to the license. This includes other CC- 245 | licensed material, or material used under an exception or 246 | limitation to copyright. More considerations for licensors: 247 | wiki.creativecommons.org/Considerations_for_licensors 248 | 249 | Considerations for the public: By using one of our public 250 | licenses, a licensor grants the public permission to use the 251 | licensed material under specified terms and conditions. If 252 | the licensor's permission is not necessary for any reason--for 253 | example, because of any applicable exception or limitation to 254 | copyright--then that use is not regulated by the license. Our 255 | licenses grant only permissions under copyright and certain 256 | other rights that a licensor has authority to grant. Use of 257 | the licensed material may still be restricted for other 258 | reasons, including because others have copyright or other 259 | rights in the material. A licensor may make special requests, 260 | such as asking that all changes be marked or described. 261 | Although not required by our licenses, you are encouraged to 262 | respect those requests where reasonable. More_considerations 263 | for the public: 264 | wiki.creativecommons.org/Considerations_for_licensees 265 | 266 | ======================================================================= 267 | 268 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 269 | Public License 270 | 271 | By exercising the Licensed Rights (defined below), You accept and agree 272 | to be bound by the terms and conditions of this Creative Commons 273 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 274 | ("Public License"). To the extent this Public License may be 275 | interpreted as a contract, You are granted the Licensed Rights in 276 | consideration of Your acceptance of these terms and conditions, and the 277 | Licensor grants You such rights in consideration of benefits the 278 | Licensor receives from making the Licensed Material available under 279 | these terms and conditions. 280 | 281 | 282 | Section 1 -- Definitions. 283 | 284 | a. Adapted Material means material subject to Copyright and Similar 285 | Rights that is derived from or based upon the Licensed Material 286 | and in which the Licensed Material is translated, altered, 287 | arranged, transformed, or otherwise modified in a manner requiring 288 | permission under the Copyright and Similar Rights held by the 289 | Licensor. For purposes of this Public License, where the Licensed 290 | Material is a musical work, performance, or sound recording, 291 | Adapted Material is always produced where the Licensed Material is 292 | synched in timed relation with a moving image. 293 | 294 | b. Adapter's License means the license You apply to Your Copyright 295 | and Similar Rights in Your contributions to Adapted Material in 296 | accordance with the terms and conditions of this Public License. 297 | 298 | c. BY-NC-SA Compatible License means a license listed at 299 | creativecommons.org/compatiblelicenses, approved by Creative 300 | Commons as essentially the equivalent of this Public License. 301 | 302 | d. Copyright and Similar Rights means copyright and/or similar rights 303 | closely related to copyright including, without limitation, 304 | performance, broadcast, sound recording, and Sui Generis Database 305 | Rights, without regard to how the rights are labeled or 306 | categorized. For purposes of this Public License, the rights 307 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 308 | Rights. 309 | 310 | e. Effective Technological Measures means those measures that, in the 311 | absence of proper authority, may not be circumvented under laws 312 | fulfilling obligations under Article 11 of the WIPO Copyright 313 | Treaty adopted on December 20, 1996, and/or similar international 314 | agreements. 315 | 316 | f. Exceptions and Limitations means fair use, fair dealing, and/or 317 | any other exception or limitation to Copyright and Similar Rights 318 | that applies to Your use of the Licensed Material. 319 | 320 | g. License Elements means the license attributes listed in the name 321 | of a Creative Commons Public License. The License Elements of this 322 | Public License are Attribution, NonCommercial, and ShareAlike. 323 | 324 | h. Licensed Material means the artistic or literary work, database, 325 | or other material to which the Licensor applied this Public 326 | License. 327 | 328 | i. Licensed Rights means the rights granted to You subject to the 329 | terms and conditions of this Public License, which are limited to 330 | all Copyright and Similar Rights that apply to Your use of the 331 | Licensed Material and that the Licensor has authority to license. 332 | 333 | j. Licensor means the individual(s) or entity(ies) granting rights 334 | under this Public License. 335 | 336 | k. NonCommercial means not primarily intended for or directed towards 337 | commercial advantage or monetary compensation. For purposes of 338 | this Public License, the exchange of the Licensed Material for 339 | other material subject to Copyright and Similar Rights by digital 340 | file-sharing or similar means is NonCommercial provided there is 341 | no payment of monetary compensation in connection with the 342 | exchange. 343 | 344 | l. Share means to provide material to the public by any means or 345 | process that requires permission under the Licensed Rights, such 346 | as reproduction, public display, public performance, distribution, 347 | dissemination, communication, or importation, and to make material 348 | available to the public including in ways that members of the 349 | public may access the material from a place and at a time 350 | individually chosen by them. 351 | 352 | m. Sui Generis Database Rights means rights other than copyright 353 | resulting from Directive 96/9/EC of the European Parliament and of 354 | the Council of 11 March 1996 on the legal protection of databases, 355 | as amended and/or succeeded, as well as other essentially 356 | equivalent rights anywhere in the world. 357 | 358 | n. You means the individual or entity exercising the Licensed Rights 359 | under this Public License. Your has a corresponding meaning. 360 | 361 | 362 | Section 2 -- Scope. 363 | 364 | a. License grant. 365 | 366 | 1. Subject to the terms and conditions of this Public License, 367 | the Licensor hereby grants You a worldwide, royalty-free, 368 | non-sublicensable, non-exclusive, irrevocable license to 369 | exercise the Licensed Rights in the Licensed Material to: 370 | 371 | a. reproduce and Share the Licensed Material, in whole or 372 | in part, for NonCommercial purposes only; and 373 | 374 | b. produce, reproduce, and Share Adapted Material for 375 | NonCommercial purposes only. 376 | 377 | 2. Exceptions and Limitations. For the avoidance of doubt, where 378 | Exceptions and Limitations apply to Your use, this Public 379 | License does not apply, and You do not need to comply with 380 | its terms and conditions. 381 | 382 | 3. Term. The term of this Public License is specified in Section 383 | 6(a). 384 | 385 | 4. Media and formats; technical modifications allowed. The 386 | Licensor authorizes You to exercise the Licensed Rights in 387 | all media and formats whether now known or hereafter created, 388 | and to make technical modifications necessary to do so. The 389 | Licensor waives and/or agrees not to assert any right or 390 | authority to forbid You from making technical modifications 391 | necessary to exercise the Licensed Rights, including 392 | technical modifications necessary to circumvent Effective 393 | Technological Measures. For purposes of this Public License, 394 | simply making modifications authorized by this Section 2(a) 395 | (4) never produces Adapted Material. 396 | 397 | 5. Downstream recipients. 398 | 399 | a. Offer from the Licensor -- Licensed Material. Every 400 | recipient of the Licensed Material automatically 401 | receives an offer from the Licensor to exercise the 402 | Licensed Rights under the terms and conditions of this 403 | Public License. 404 | 405 | b. Additional offer from the Licensor -- Adapted Material. 406 | Every recipient of Adapted Material from You 407 | automatically receives an offer from the Licensor to 408 | exercise the Licensed Rights in the Adapted Material 409 | under the conditions of the Adapter's License You apply. 410 | 411 | c. No downstream restrictions. You may not offer or impose 412 | any additional or different terms or conditions on, or 413 | apply any Effective Technological Measures to, the 414 | Licensed Material if doing so restricts exercise of the 415 | Licensed Rights by any recipient of the Licensed 416 | Material. 417 | 418 | 6. No endorsement. Nothing in this Public License constitutes or 419 | may be construed as permission to assert or imply that You 420 | are, or that Your use of the Licensed Material is, connected 421 | with, or sponsored, endorsed, or granted official status by, 422 | the Licensor or others designated to receive attribution as 423 | provided in Section 3(a)(1)(A)(i). 424 | 425 | b. Other rights. 426 | 427 | 1. Moral rights, such as the right of integrity, are not 428 | licensed under this Public License, nor are publicity, 429 | privacy, and/or other similar personality rights; however, to 430 | the extent possible, the Licensor waives and/or agrees not to 431 | assert any such rights held by the Licensor to the limited 432 | extent necessary to allow You to exercise the Licensed 433 | Rights, but not otherwise. 434 | 435 | 2. Patent and trademark rights are not licensed under this 436 | Public License. 437 | 438 | 3. To the extent possible, the Licensor waives any right to 439 | collect royalties from You for the exercise of the Licensed 440 | Rights, whether directly or through a collecting society 441 | under any voluntary or waivable statutory or compulsory 442 | licensing scheme. In all other cases the Licensor expressly 443 | reserves any right to collect such royalties, including when 444 | the Licensed Material is used other than for NonCommercial 445 | purposes. 446 | 447 | 448 | Section 3 -- License Conditions. 449 | 450 | Your exercise of the Licensed Rights is expressly made subject to the 451 | following conditions. 452 | 453 | a. Attribution. 454 | 455 | 1. If You Share the Licensed Material (including in modified 456 | form), You must: 457 | 458 | a. retain the following if it is supplied by the Licensor 459 | with the Licensed Material: 460 | 461 | i. identification of the creator(s) of the Licensed 462 | Material and any others designated to receive 463 | attribution, in any reasonable manner requested by 464 | the Licensor (including by pseudonym if 465 | designated); 466 | 467 | ii. a copyright notice; 468 | 469 | iii. a notice that refers to this Public License; 470 | 471 | iv. a notice that refers to the disclaimer of 472 | warranties; 473 | 474 | v. a URI or hyperlink to the Licensed Material to the 475 | extent reasonably practicable; 476 | 477 | b. indicate if You modified the Licensed Material and 478 | retain an indication of any previous modifications; and 479 | 480 | c. indicate the Licensed Material is licensed under this 481 | Public License, and include the text of, or the URI or 482 | hyperlink to, this Public License. 483 | 484 | 2. You may satisfy the conditions in Section 3(a)(1) in any 485 | reasonable manner based on the medium, means, and context in 486 | which You Share the Licensed Material. For example, it may be 487 | reasonable to satisfy the conditions by providing a URI or 488 | hyperlink to a resource that includes the required 489 | information. 490 | 3. If requested by the Licensor, You must remove any of the 491 | information required by Section 3(a)(1)(A) to the extent 492 | reasonably practicable. 493 | 494 | b. ShareAlike. 495 | 496 | In addition to the conditions in Section 3(a), if You Share 497 | Adapted Material You produce, the following conditions also apply. 498 | 499 | 1. The Adapter's License You apply must be a Creative Commons 500 | license with the same License Elements, this version or 501 | later, or a BY-NC-SA Compatible License. 502 | 503 | 2. You must include the text of, or the URI or hyperlink to, the 504 | Adapter's License You apply. You may satisfy this condition 505 | in any reasonable manner based on the medium, means, and 506 | context in which You Share Adapted Material. 507 | 508 | 3. You may not offer or impose any additional or different terms 509 | or conditions on, or apply any Effective Technological 510 | Measures to, Adapted Material that restrict exercise of the 511 | rights granted under the Adapter's License You apply. 512 | 513 | 514 | Section 4 -- Sui Generis Database Rights. 515 | 516 | Where the Licensed Rights include Sui Generis Database Rights that 517 | apply to Your use of the Licensed Material: 518 | 519 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 520 | to extract, reuse, reproduce, and Share all or a substantial 521 | portion of the contents of the database for NonCommercial purposes 522 | only; 523 | 524 | b. if You include all or a substantial portion of the database 525 | contents in a database in which You have Sui Generis Database 526 | Rights, then the database in which You have Sui Generis Database 527 | Rights (but not its individual contents) is Adapted Material, 528 | including for purposes of Section 3(b); and 529 | 530 | c. You must comply with the conditions in Section 3(a) if You Share 531 | all or a substantial portion of the contents of the database. 532 | 533 | For the avoidance of doubt, this Section 4 supplements and does not 534 | replace Your obligations under this Public License where the Licensed 535 | Rights include other Copyright and Similar Rights. 536 | 537 | 538 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 539 | 540 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 541 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 542 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 543 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 544 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 545 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 546 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 547 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 548 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 549 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 550 | 551 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 552 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 553 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 554 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 555 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 556 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 557 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 558 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 559 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 560 | 561 | c. The disclaimer of warranties and limitation of liability provided 562 | above shall be interpreted in a manner that, to the extent 563 | possible, most closely approximates an absolute disclaimer and 564 | waiver of all liability. 565 | 566 | 567 | Section 6 -- Term and Termination. 568 | 569 | a. This Public License applies for the term of the Copyright and 570 | Similar Rights licensed here. However, if You fail to comply with 571 | this Public License, then Your rights under this Public License 572 | terminate automatically. 573 | 574 | b. Where Your right to use the Licensed Material has terminated under 575 | Section 6(a), it reinstates: 576 | 577 | 1. automatically as of the date the violation is cured, provided 578 | it is cured within 30 days of Your discovery of the 579 | violation; or 580 | 581 | 2. upon express reinstatement by the Licensor. 582 | 583 | For the avoidance of doubt, this Section 6(b) does not affect any 584 | right the Licensor may have to seek remedies for Your violations 585 | of this Public License. 586 | 587 | c. For the avoidance of doubt, the Licensor may also offer the 588 | Licensed Material under separate terms or conditions or stop 589 | distributing the Licensed Material at any time; however, doing so 590 | will not terminate this Public License. 591 | 592 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 593 | License. 594 | 595 | 596 | Section 7 -- Other Terms and Conditions. 597 | 598 | a. The Licensor shall not be bound by any additional or different 599 | terms or conditions communicated by You unless expressly agreed. 600 | 601 | b. Any arrangements, understandings, or agreements regarding the 602 | Licensed Material not stated herein are separate from and 603 | independent of the terms and conditions of this Public License. 604 | 605 | 606 | Section 8 -- Interpretation. 607 | 608 | a. For the avoidance of doubt, this Public License does not, and 609 | shall not be interpreted to, reduce, limit, restrict, or impose 610 | conditions on any use of the Licensed Material that could lawfully 611 | be made without permission under this Public License. 612 | 613 | b. To the extent possible, if any provision of this Public License is 614 | deemed unenforceable, it shall be automatically reformed to the 615 | minimum extent necessary to make it enforceable. If the provision 616 | cannot be reformed, it shall be severed from this Public License 617 | without affecting the enforceability of the remaining terms and 618 | conditions. 619 | 620 | c. No term or condition of this Public License will be waived and no 621 | failure to comply consented to unless expressly agreed to by the 622 | Licensor. 623 | 624 | d. Nothing in this Public License constitutes or may be interpreted 625 | as a limitation upon, or waiver of, any privileges and immunities 626 | that apply to the Licensor or You, including from the legal 627 | processes of any jurisdiction or authority. 628 | 629 | ======================================================================= 630 | 631 | Creative Commons is not a party to its public licenses. 632 | Notwithstanding, Creative Commons may elect to apply one of its public 633 | licenses to material it publishes and in those instances will be 634 | considered the "Licensor." Except for the limited purpose of indicating 635 | that material is shared under a Creative Commons public license or as 636 | otherwise permitted by the Creative Commons policies published at 637 | creativecommons.org/policies, Creative Commons does not authorize the 638 | use of the trademark "Creative Commons" or any other trademark or logo 639 | of Creative Commons without its prior written consent including, 640 | without limitation, in connection with any unauthorized modifications 641 | to any of its public licenses or any other arrangements, 642 | understandings, or agreements concerning use of licensed material. For 643 | the avoidance of doubt, this paragraph does not form part of the public 644 | licenses. 645 | 646 | Creative Commons may be contacted at creativecommons.org. 647 | 648 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo has been moved to [apps-script-samples](https://github.com/gsuitedevs/apps-script-samples/tree/master/android/mobile-translate). 2 | 3 | --- 4 | 5 | ## Apps Script Mobile Add-ons 6 | 7 | Android applications that interact with the Google Docs and Sheets 8 | editor apps, and use the 9 | [Apps Script Execution API](https://developers.google.com/apps-script/guides/) 10 | to call Apps Script functions to interact with Google services. 11 | 12 | Introduction 13 | ------------ 14 | 15 | Google Apps Script now allows developers to construct mobile add-ons -- 16 | Android applications which extend and support Google Docs and Sheets. 17 | 18 | This repository contains sample mobile add-ons that are meant to 19 | provide an example of a mobile add-on's project structure. 20 | 21 | Learn more 22 | ---------- 23 | 24 | To continue learning about mobile add-ons for Google Docs and Sheets, 25 | take a look at the following resources: 26 | 27 | * [Mobile Add-ons](https://developers.google.com/apps-script/add-ons/mobile) 28 | * [Apps Script Execution API](https://developers.google.com/apps-script/guides/) 29 | 30 | Support 31 | ------- 32 | 33 | For general Apps Script support, check the following: 34 | 35 | - Stack Overflow Tag: [google-apps-script](http://stackoverflow.com/questions/tagged/google-apps-script) 36 | - Issue Tracker: [google-apps-script-issues](https://code.google.com/p/google-apps-script-issues/issues/list) 37 | 38 | If you've found an error in this sample, please file an issue: 39 | https://github.com/googlesamples/apps-script-mobile-addons 40 | 41 | Patches are encouraged, and may be submitted by forking this project and 42 | submitting a pull request through GitHub. 43 | 44 | License 45 | ------- 46 | 47 | Copyright 2016 Google, Inc. 48 | 49 | Licensed to the Apache Software Foundation (ASF) under one 50 | or more contributor license agreements. See the NOTICE file 51 | distributed with this work for additional information 52 | regarding copyright ownership. The ASF licenses this file 53 | to you under the Apache License, Version 2.0 (the 54 | "License"); you may not use this file except in compliance 55 | with the License. You may obtain a copy of the License at 56 | 57 | http://www.apache.org/licenses/LICENSE-2.0 58 | 59 | Unless required by applicable law or agreed to in writing, 60 | software distributed under the License is distributed on an 61 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 62 | KIND, either express or implied. See the License for the 63 | specific language governing permissions and limitations 64 | under the License. 65 | -------------------------------------------------------------------------------- /mobile-translate/Code.gs: -------------------------------------------------------------------------------- 1 | /** 2 | * @OnlyCurrentDoc 3 | * 4 | * The above comment directs Apps Script to limit the scope of file 5 | * access for this add-on. It specifies that this add-on will only 6 | * attempt to read or modify the files in which the add-on is used, 7 | * and not all of the user's files. The authorization request message 8 | * presented to users will reflect this limited scope. 9 | */ 10 | 11 | /** 12 | * Creates a menu entry in the Google Docs UI when the document is opened. 13 | * This method is only used by the regular add-on, and is never called by 14 | * the mobile add-on version. 15 | * 16 | * @param {object} e The event parameter for a simple onOpen trigger. To 17 | * determine which authorization mode (ScriptApp.AuthMode) the trigger is 18 | * running in, inspect e.authMode. 19 | */ 20 | function onOpen(e) { 21 | DocumentApp.getUi().createAddonMenu() 22 | .addItem('Start', 'showSidebar') 23 | .addToUi(); 24 | } 25 | 26 | /** 27 | * Runs when the add-on is installed. 28 | * This method is only used by the regular add-on, and is never called by 29 | * the mobile add-on version. 30 | * 31 | * @param {object} e The event parameter for a simple onInstall trigger. To 32 | * determine which authorization mode (ScriptApp.AuthMode) the trigger is 33 | * running in, inspect e.authMode. (In practice, onInstall triggers always 34 | * run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or 35 | * AuthMode.NONE.) 36 | */ 37 | function onInstall(e) { 38 | onOpen(e); 39 | } 40 | 41 | /** 42 | * Opens a sidebar in the document containing the add-on's user interface. 43 | * This method is only used by the regular add-on, and is never called by 44 | * the mobile add-on version. 45 | */ 46 | function showSidebar() { 47 | var ui = HtmlService.createHtmlOutputFromFile('Sidebar') 48 | .setTitle('Translate'); 49 | DocumentApp.getUi().showSidebar(ui); 50 | } 51 | 52 | /** 53 | * Gets the text the user has selected. If there is no selection, 54 | * this function displays an error message. 55 | * 56 | * @return {Array.} The selected text. 57 | */ 58 | function getSelectedText() { 59 | var selection = DocumentApp.getActiveDocument().getSelection(); 60 | if (selection) { 61 | var text = []; 62 | var elements = selection.getSelectedElements(); 63 | for (var i = 0; i < elements.length; i++) { 64 | if (elements[i].isPartial()) { 65 | var element = elements[i].getElement().asText(); 66 | var startIndex = elements[i].getStartOffset(); 67 | var endIndex = elements[i].getEndOffsetInclusive(); 68 | 69 | text.push(element.getText().substring(startIndex, endIndex + 1)); 70 | } else { 71 | var element = elements[i].getElement(); 72 | // Only translate elements that can be edited as text; skip images and 73 | // other non-text elements. 74 | if (element.editAsText) { 75 | var elementText = element.asText().getText(); 76 | // This check is necessary to exclude images, which return a blank 77 | // text element. 78 | if (elementText != '') { 79 | text.push(elementText); 80 | } 81 | } 82 | } 83 | } 84 | if (text.length == 0) { 85 | throw 'Please select some text.'; 86 | } 87 | return text; 88 | } else { 89 | throw 'Please select some text.'; 90 | } 91 | } 92 | 93 | /** 94 | * Gets the stored user preferences for the origin and destination languages, 95 | * if they exist. 96 | * This method is only used by the regular add-on, and is never called by 97 | * the mobile add-on version. 98 | * 99 | * @return {Object} The user's origin and destination language preferences, if 100 | * they exist. 101 | */ 102 | function getPreferences() { 103 | var userProperties = PropertiesService.getUserProperties(); 104 | var languagePrefs = { 105 | originLang: userProperties.getProperty('originLang'), 106 | destLang: userProperties.getProperty('destLang') 107 | }; 108 | return languagePrefs; 109 | } 110 | 111 | /** 112 | * Gets the user-selected text and translates it from the origin language to the 113 | * destination language. The languages are notated by their two-letter short 114 | * form. For example, English is 'en', and Spanish is 'es'. The origin language 115 | * may be specified as an empty string to indicate that Google Translate should 116 | * auto-detect the language. 117 | * 118 | * @param {string} origin The two-letter short form for the origin language. 119 | * @param {string} dest The two-letter short form for the destination language. 120 | * @param {boolean} savePrefs Whether to save the origin and destination 121 | * language preferences. 122 | * @return {Object} Object containing the original text and the result of the 123 | * translation. 124 | */ 125 | function getTextAndTranslation(origin, dest, savePrefs) { 126 | var result = {}; 127 | var text = getSelectedText(); 128 | result['text'] = text.join('\n'); 129 | 130 | if (savePrefs == true) { 131 | var userProperties = PropertiesService.getUserProperties(); 132 | userProperties.setProperty('originLang', origin); 133 | userProperties.setProperty('destLang', dest); 134 | } 135 | 136 | result['translation'] = translateText(result['text'], origin, dest); 137 | 138 | return result; 139 | } 140 | 141 | /** 142 | * Replaces the text of the current selection with the provided text, or 143 | * inserts text at the current cursor location. (There will always be either 144 | * a selection or a cursor.) If multiple elements are selected, only inserts the 145 | * translated text in the first element that can contain text and removes the 146 | * other elements. 147 | * 148 | * @param {string} newText The text with which to replace the current selection. 149 | */ 150 | function insertText(newText) { 151 | var selection = DocumentApp.getActiveDocument().getSelection(); 152 | if (selection) { 153 | var replaced = false; 154 | var elements = selection.getSelectedElements(); 155 | if (elements.length == 1 && 156 | elements[0].getElement().getType() == 157 | DocumentApp.ElementType.INLINE_IMAGE) { 158 | throw "Can't insert text into an image."; 159 | } 160 | for (var i = 0; i < elements.length; i++) { 161 | if (elements[i].isPartial()) { 162 | var element = elements[i].getElement().asText(); 163 | var startIndex = elements[i].getStartOffset(); 164 | var endIndex = elements[i].getEndOffsetInclusive(); 165 | 166 | var remainingText = element.getText().substring(endIndex + 1); 167 | element.deleteText(startIndex, endIndex); 168 | if (!replaced) { 169 | element.insertText(startIndex, newText); 170 | replaced = true; 171 | } else { 172 | // This block handles a selection that ends with a partial element. We 173 | // want to copy this partial text to the previous element so we don't 174 | // have a line-break before the last partial. 175 | var parent = element.getParent(); 176 | parent.getPreviousSibling().asText().appendText(remainingText); 177 | // We cannot remove the last paragraph of a doc. If this is the case, 178 | // just remove the text within the last paragraph instead. 179 | if (parent.getNextSibling()) { 180 | parent.removeFromParent(); 181 | } else { 182 | element.removeFromParent(); 183 | } 184 | } 185 | } else { 186 | var element = elements[i].getElement(); 187 | if (!replaced && element.editAsText) { 188 | // Only translate elements that can be edited as text, removing other 189 | // elements. 190 | element.clear(); 191 | element.asText().setText(newText); 192 | replaced = true; 193 | } else { 194 | // We cannot remove the last paragraph of a doc. If this is the case, 195 | // just clear the element. 196 | if (element.getNextSibling()) { 197 | element.removeFromParent(); 198 | } else { 199 | element.clear(); 200 | } 201 | } 202 | } 203 | } 204 | } else { 205 | var cursor = DocumentApp.getActiveDocument().getCursor(); 206 | var surroundingText = cursor.getSurroundingText().getText(); 207 | var surroundingTextOffset = cursor.getSurroundingTextOffset(); 208 | 209 | // If the cursor follows or preceds a non-space character, insert a space 210 | // between the character and the translation. Otherwise, just insert the 211 | // translation. 212 | if (surroundingTextOffset > 0) { 213 | if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') { 214 | newText = ' ' + newText; 215 | } 216 | } 217 | if (surroundingTextOffset < surroundingText.length) { 218 | if (surroundingText.charAt(surroundingTextOffset) != ' ') { 219 | newText += ' '; 220 | } 221 | } 222 | cursor.insertText(newText); 223 | } 224 | } 225 | 226 | 227 | /** 228 | * Given text, translate it from the origin language to the destination 229 | * language. The languages are notated by their two-letter short form. For 230 | * example, English is 'en', and Spanish is 'es'. The origin language may be 231 | * specified as an empty string to indicate that Google Translate should 232 | * auto-detect the language. 233 | * 234 | * @param {string} text text to translate. 235 | * @param {string} origin The two-letter short form for the origin language. 236 | * @param {string} dest The two-letter short form for the destination language. 237 | * @return {string} The result of the translation, or the original text if 238 | * origin and dest languages are the same. 239 | */ 240 | function translateText(text, origin, dest) { 241 | if (origin === dest) { 242 | return text; 243 | } 244 | return LanguageApp.translate(text, origin, dest); 245 | } 246 | -------------------------------------------------------------------------------- /mobile-translate/README.md: -------------------------------------------------------------------------------- 1 | Mobile Doc Translate Add-on 2 | =========================== 3 | 4 | A sample Google Apps Script mobile add-on for Google Docs. This add-on is 5 | essentially a mobile version of the Docs 6 | [Translate Add-on Quickstart](https://developers.google.com/apps-script/quickstart/docs). 7 | 8 | Introduction 9 | ------------ 10 | 11 | Google Apps Script now allows developers to construct Mobile Add-ons -- Android 12 | applications which extend and support Google Docs and Sheets. 13 | 14 | This sample shows how to construct a mobile add-on called 15 | **Mobile Doc Translate**. This add-on allows users to select text in a 16 | Google Doc on their mobile device and see a translation of that text in one 17 | of several languages. The user can then edit the translation as needed and 18 | replace the original selected text in the Doc with the translation. 19 | 20 | 21 | Getting Started 22 | --------------- 23 | 24 | The add-on will need to call an Apps Script project to get Doc text, make 25 | translations, and insert text into the Doc. Users can access this add-on from 26 | the Google Docs Android app by highlighting text and selecting the add-on in the 27 | text context menu. 28 | 29 | The Apps Script code file for this project is `Code.gs`. This is the same code 30 | used in the [Translate Add-on Quickstart](https://developers.google.com/apps-script/quickstart/docs), 31 | but does not include the HTML code that defines the quickstart's sidebar. 32 | 33 | The mobile add-on will make use of the 34 | [Apps Script Execution API](https://developers.google.com/apps-script/guides/rest/) 35 | to call the `Code.gs` functions. The 36 | [Execution API quickstart for Android](https://developers.google.com/apps-script/guides/rest/quickstart/android) 37 | describes how to call Apps Script functions from Android applications. 38 | 39 | To build this sample: 40 | 41 | 1. The `app/` folder in this repository contains all the required Android files 42 | for this add-on. These can be manually copied or imported into a new Android 43 | Studio project. 44 | 1. Create a new Apps Script project. 45 | 1. Replace the code in the new project's `Code.gs` file with the code from this 46 | repo. 47 | 1. Save the project. 48 | 1. In the code editor, select **Publish > Deploy as API** executable. 49 | 1. In the dialog that opens, leave the **Version** as "New" and enter 50 | "Target-v1" into the text box. Click **Deploy**. 51 | 1. Follow the 52 | [Keytool SHA1 Fingerprint](https://developers.google.com/apps-script/guides/rest/quickstart/android#step_1_acquire_a_sha1_fingerprint) 53 | instructions to acquire a SHA1 fingerprint for your project. 54 | 1. Using that SHA code, follow the 55 | [Turn on the Execution API](https://developers.google.com/apps-script/guides/rest/quickstart/android#step_2_turn_on_the_api_name) 56 | instructions to enable the API for your script project and create OAuth 57 | credentials. Be sure to match the same package name used in your Android 58 | code. 59 | 1. Edit the `MainActivity.java` file so that the `SCRIPT_ID` constant is set to 60 | your Apps Script project ID (in the script editor, select 61 | **File > Project properties**, and use the **Project key**). 62 | 63 | These steps should allow you to build the Android app and have it successfully 64 | call the Apps Script code. You can test it by: 65 | 66 | 1. Install the app on a test Android device. 67 | 1. Set the app as the debug app on the device by running this 68 | [ADB](https://developer.android.com/studio/command-line/adb.html) 69 | command: 70 | `$ adb shell am set-debug-app --persistent ` 71 | 1. Open a docucment using the Google Docs app on the device. 72 | 1. Highlight some text in the doc and select the three-dot icon to open the 73 | context menu, and then select **Mobile Doc Translate**. 74 | 75 | Learn more 76 | ---------- 77 | 78 | To continue learning about mobile add-ons for Google Docs and Sheets, 79 | take a look at the following resources: 80 | 81 | * [Mobile Add-ons](https://developers.google.com/apps-script/add-ons/mobile) 82 | * [Apps Script Execution API](https://developers.google.com/apps-script/guides/) 83 | 84 | Support 85 | ------- 86 | 87 | For general Apps Script support, check the following: 88 | 89 | - Stack Overflow Tag: [google-apps-script](http://stackoverflow.com/questions/tagged/google-apps-script) 90 | - Issue Tracker: [google-apps-script-issues](https://code.google.com/p/google-apps-script-issues/issues/list) 91 | 92 | If you've found an error in this sample, please file an issue: 93 | https://github.com/googlesamples/apps-script-mobile-addons 94 | 95 | Patches are encouraged, and may be submitted by forking this project and 96 | submitting a pull request through GitHub. 97 | 98 | License 99 | ------- 100 | 101 | Copyright 2016 Google, Inc. 102 | 103 | Licensed to the Apache Software Foundation (ASF) under one 104 | or more contributor license agreements. See the NOTICE file 105 | distributed with this work for additional information 106 | regarding copyright ownership. The ASF licenses this file 107 | to you under the Apache License, Version 2.0 (the 108 | "License"); you may not use this file except in compliance 109 | with the License. You may obtain a copy of the License at 110 | 111 | http://www.apache.org/licenses/LICENSE-2.0 112 | 113 | Unless required by applicable law or agreed to in writing, 114 | software distributed under the License is distributed on an 115 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 116 | KIND, either express or implied. See the License for the 117 | specific language governing permissions and limitations 118 | under the License. -------------------------------------------------------------------------------- /mobile-translate/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def keystorePropertiesFile = rootProject.file("keystore.properties") 4 | def keystoreProperties = new Properties() 5 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 6 | 7 | android { 8 | signingConfigs { 9 | mainRelease { 10 | keyAlias keystoreProperties['keyAlias'] 11 | keyPassword keystoreProperties['keyPassword'] 12 | storeFile file(keystoreProperties['storeFile']) 13 | storePassword keystoreProperties['storePassword'] 14 | } 15 | } 16 | compileSdkVersion 23 17 | buildToolsVersion "24.0.0 rc4" 18 | defaultConfig { 19 | applicationId "com.google.samples.mobiledoctranslate" 20 | minSdkVersion 17 21 | targetSdkVersion 23 22 | versionCode 2 23 | versionName "1.0.1" 24 | } 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 29 | 'proguard-rules.pro' 30 | signingConfig signingConfigs.mainRelease 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | compile fileTree(include: ['*.jar'], dir: 'libs') 37 | testCompile 'junit:junit:4.12' 38 | compile 'com.android.support:appcompat-v7:23.4.0' 39 | compile 'com.google.android.gms:play-services-auth:9.0.2' 40 | compile 'com.android.support:cardview-v7:23.4.0' 41 | compile 'pub.devrel:easypermissions:0.1.5' 42 | compile('com.google.api-client:google-api-client-android:1.20.0') { 43 | exclude group: 'org.apache.httpcomponents' 44 | } 45 | compile('com.google.apis:google-api-services-script:v1-rev1-1.20.0') { 46 | exclude group: 'org.apache.httpcomponents' 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mobile-translate/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 17 | 20 | 21 | 25 | 27 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/DefaultLaunchActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.mobiledoctranslate; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import android.view.View; 22 | 23 | /** 24 | * Since this add-on needs context from the Docs editor app, it should only be 25 | * launched from that app (via context menus). 26 | * 27 | * This activity handles the edge case where the app is (erroneously) launched 28 | * from the home screen or a notification. This activity simply presents a 29 | * message to the user and provides an Exit button. 30 | */ 31 | public class DefaultLaunchActivity extends Activity { 32 | 33 | /** 34 | * Create the default launch activity. 35 | * @param savedInstanceState previously saved instance data 36 | */ 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.launch_default); 41 | } 42 | 43 | /** 44 | * Cancel the add-on and return without action. 45 | * @param v The button's View context 46 | */ 47 | public void cancel(View v) { 48 | setResult(Activity.RESULT_CANCELED); 49 | finish(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/MainActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.mobiledoctranslate; 18 | 19 | import com.google.android.gms.auth.GoogleAuthException; 20 | import com.google.android.gms.common.ConnectionResult; 21 | import com.google.android.gms.common.GoogleApiAvailability; 22 | import com.google.api.client.extensions.android.http.AndroidHttp; 23 | import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; 24 | import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException; 25 | import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException; 26 | import com.google.api.client.http.HttpRequest; 27 | import com.google.api.client.http.HttpRequestInitializer; 28 | import com.google.api.client.http.HttpTransport; 29 | import com.google.api.client.json.JsonFactory; 30 | import com.google.api.client.json.jackson2.JacksonFactory; 31 | import com.google.api.client.util.ExponentialBackOff; 32 | import com.google.api.services.script.model.*; 33 | import com.google.api.services.script.Script; 34 | 35 | import android.Manifest; 36 | import android.accounts.Account; 37 | import android.app.Activity; 38 | import android.app.AlertDialog; 39 | import android.app.Dialog; 40 | import android.app.ProgressDialog; 41 | import android.content.BroadcastReceiver; 42 | import android.content.Context; 43 | import android.content.ContextWrapper; 44 | import android.content.DialogInterface; 45 | import android.content.Intent; 46 | import android.content.IntentFilter; 47 | import android.content.SharedPreferences; 48 | import android.net.ConnectivityManager; 49 | import android.net.NetworkInfo; 50 | import android.os.AsyncTask; 51 | import android.os.Bundle; 52 | import android.support.annotation.NonNull; 53 | import android.text.method.ScrollingMovementMethod; 54 | import android.view.View; 55 | import android.widget.AdapterView; 56 | import android.widget.Button; 57 | import android.widget.EditText; 58 | import android.widget.Spinner; 59 | import android.widget.TextView; 60 | import android.widget.Toast; 61 | 62 | import java.io.IOException; 63 | import java.lang.StringBuilder; 64 | import java.util.Arrays; 65 | import java.util.List; 66 | import java.util.Map; 67 | 68 | import pub.devrel.easypermissions.AfterPermissionGranted; 69 | import pub.devrel.easypermissions.EasyPermissions; 70 | 71 | /** 72 | * This is the main (and only) activity of the add-on. It shows the user what 73 | * text or cells were selected, the results of translation and provides some 74 | * UI controls. 75 | */ 76 | public class MainActivity extends Activity 77 | implements EasyPermissions.PermissionCallbacks { 78 | 79 | /** 80 | * The script ID for the Apps Script the add-on will call 81 | */ 82 | private static final String SCRIPT_ID = "ENTER_YOUR_SCRIPT_ID_HERE"; 83 | 84 | // Constants 85 | private static final String FUNCTION_GET_TEXT = "getTextAndTranslation"; 86 | private static final String FUNCTION_TRANSLATE_TEXT = "translateText"; 87 | private static final String FUNCTION_INSERT_TEXT = "insertText"; 88 | static final int REQUEST_AUTHORIZATION = 1001; 89 | static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002; 90 | static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003; 91 | private static final String[] SCOPES = { 92 | "https://www.googleapis.com/auth/documents.currentonly", 93 | "https://www.googleapis.com/auth/script.scriptapp", 94 | "https://www.googleapis.com/auth/script.storage" 95 | }; 96 | static final String SAVED_ORIG_LANG = "origLangPosition"; 97 | static final String SAVED_DEST_LANG = "destLangPosition"; 98 | private static final int CALL_GET_TEXT = 0; 99 | private static final int CALL_TRANSLATE_TEXT = 1; 100 | private static final int CALL_REPLACE_TEXT = 2; 101 | 102 | /** 103 | * An Apps Script API service object used to access the API, and related 104 | * objects 105 | */ 106 | Script mService = null; 107 | GoogleAccountCredential mCredential = null; 108 | final HttpTransport mTransport = AndroidHttp.newCompatibleTransport(); 109 | final JsonFactory mJsonFactory = JacksonFactory.getDefaultInstance(); 110 | 111 | // Layout components 112 | private TextView mSelectedText; 113 | private EditText mTranslationText; 114 | private Button mReplaceButton; 115 | private ProgressDialog mProgress; 116 | 117 | // Translation language controls 118 | private String mOrigLang; 119 | private String mDestLang; 120 | private int mPrevOrigSpinnerPos; 121 | private int mPrevDestSpinnerPos; 122 | 123 | // Other variables 124 | private NetworkReceiver mReceiver; 125 | private boolean mConnectionAvailable; 126 | private int mLastFunctionCalled; 127 | private String mState; 128 | private Account mAccount; 129 | 130 | /** 131 | * Create the main activity. 132 | * @param savedInstanceState previously saved instance data 133 | */ 134 | @Override 135 | protected void onCreate(Bundle savedInstanceState) { 136 | super.onCreate(savedInstanceState); 137 | setContentView(R.layout.activity_main); 138 | 139 | // Verify the add-on was called from the Docs editor. 140 | if (! "com.google.android.apps.docs.editors.docs".equals( 141 | getCallingPackage())) { 142 | showErrorDialog(getString(R.string.unexpected_app) 143 | + getCallingPackage()); 144 | } 145 | 146 | // Acquire the doc/sheet state from the incoming intent. 147 | // It's also possible to acquire the docId from the intent; 148 | // that is not used in this example, however. 149 | mState = getIntent().getStringExtra( 150 | "com.google.android.apps.docs.addons.SessionState"); 151 | mAccount = getIntent().getParcelableExtra( 152 | "com.google.android.apps.docs.addons.Account"); 153 | 154 | // Load previously chosen language selections, if any 155 | SharedPreferences settings = getPreferences(Context.MODE_PRIVATE); 156 | mPrevOrigSpinnerPos = settings.getInt(SAVED_ORIG_LANG, 0); 157 | mOrigLang = getLangIdFromSpinnerPosition(mPrevOrigSpinnerPos, false); 158 | mPrevDestSpinnerPos = settings.getInt(SAVED_DEST_LANG, 0); 159 | mDestLang = getLangIdFromSpinnerPosition(mPrevDestSpinnerPos, true); 160 | 161 | // Initialize layout objects 162 | mProgress = new ProgressDialog(MainActivity.this); 163 | 164 | mSelectedText = (TextView) findViewById(R.id.selected_text); 165 | mSelectedText.setVerticalScrollBarEnabled(true); 166 | mSelectedText.setMovementMethod(new ScrollingMovementMethod()); 167 | 168 | mTranslationText = (EditText) findViewById(R.id.translated_text); 169 | mTranslationText.setVerticalScrollBarEnabled(true); 170 | mTranslationText.setMovementMethod(new ScrollingMovementMethod()); 171 | 172 | mReplaceButton = (Button) findViewById(R.id.replace_button); 173 | 174 | Spinner origLangSpinner = (Spinner) findViewById(R.id.origin_lang); 175 | Spinner destLangSpinner = (Spinner) findViewById(R.id.dest_lang); 176 | origLangSpinner.setSelection(mPrevOrigSpinnerPos); 177 | destLangSpinner.setSelection(mPrevDestSpinnerPos); 178 | origLangSpinner.setOnItemSelectedListener( 179 | new AdapterView.OnItemSelectedListener() { 180 | @Override 181 | public void onItemSelected( 182 | AdapterView parent, View view, int pos, long id) { 183 | if (pos != mPrevOrigSpinnerPos) { 184 | mPrevOrigSpinnerPos = pos; 185 | mOrigLang = getLangIdFromSpinnerPosition(pos, false); 186 | SharedPreferences settings = 187 | getPreferences(Context.MODE_PRIVATE); 188 | SharedPreferences.Editor editor = settings.edit(); 189 | editor.putInt(SAVED_ORIG_LANG, pos); 190 | editor.apply(); 191 | translate(); 192 | } 193 | } 194 | 195 | @Override 196 | public void onNothingSelected(AdapterView parent) {} 197 | }); 198 | destLangSpinner.setOnItemSelectedListener( 199 | new AdapterView.OnItemSelectedListener() { 200 | @Override 201 | public void onItemSelected( 202 | AdapterView parent, View view, int pos, long id) { 203 | if (pos != mPrevDestSpinnerPos) { 204 | mPrevDestSpinnerPos = pos; 205 | mDestLang = getLangIdFromSpinnerPosition(pos, true); 206 | SharedPreferences settings = 207 | getPreferences(Context.MODE_PRIVATE); 208 | SharedPreferences.Editor editor = settings.edit(); 209 | editor.putInt(SAVED_DEST_LANG, pos); 210 | editor.apply(); 211 | translate(); 212 | } 213 | } 214 | 215 | @Override 216 | public void onNothingSelected(AdapterView parent) {} 217 | }); 218 | 219 | // Register BroadcastReceiver to track connection changes, and 220 | // determine if a connection is initially available 221 | IntentFilter filter = 222 | new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 223 | mReceiver = new NetworkReceiver(); 224 | MainActivity.this.registerReceiver(mReceiver, filter); 225 | updateButtonEnableStatus(); 226 | 227 | // Start the add-on by attempting to retrieve the selected text from 228 | // the Doc that fired the add-on 229 | callAppsScriptTask(CALL_GET_TEXT); 230 | } 231 | 232 | /** 233 | * Extend the given HttpRequestInitializer (usually a Credentials object) 234 | * with additional initialize() instructions. 235 | * 236 | * @param requestInitializer the initializer to copy and adjust; typically 237 | * a Credential object 238 | * @return an initializer with an extended read timeout 239 | */ 240 | private static HttpRequestInitializer setHttpTimeout( 241 | final HttpRequestInitializer requestInitializer) { 242 | return new HttpRequestInitializer() { 243 | @Override 244 | public void initialize(HttpRequest httpRequest) 245 | throws java.io.IOException { 246 | requestInitializer.initialize(httpRequest); 247 | // This allows the API to call (and avoid timing out on) 248 | // functions that take up to 30 seconds to complete. Note that 249 | // the maximum allowed script run time is 6 minutes. 250 | httpRequest.setReadTimeout(30000); 251 | } 252 | }; 253 | } 254 | 255 | /** 256 | * Clean up and destroy the main activity. 257 | */ 258 | @Override 259 | public void onDestroy() { 260 | super.onDestroy(); 261 | // Unregister the connectivity broadcast receiver. 262 | if (mReceiver != null) { 263 | MainActivity.this.unregisterReceiver(mReceiver); 264 | } 265 | } 266 | 267 | /** 268 | * Called when an activity launched here (specifically, AccountPicker 269 | * and authorization) exits, giving you the requestCode you started it with, 270 | * the resultCode it returned, and any additional data from it. 271 | * @param requestCode code indicating which activity result is incoming 272 | * @param resultCode code indicating the result of the incoming 273 | * activity result 274 | * @param data Intent (containing result data) returned by incoming 275 | * activity result 276 | */ 277 | @Override 278 | protected void onActivityResult( 279 | int requestCode, int resultCode, Intent data) { 280 | super.onActivityResult(requestCode, resultCode, data); 281 | switch(requestCode) { 282 | case REQUEST_GOOGLE_PLAY_SERVICES: 283 | if (resultCode == RESULT_OK) { 284 | callAppsScriptTask(mLastFunctionCalled); 285 | } else { 286 | showErrorDialog(getString(R.string.gps_required)); 287 | } 288 | break; 289 | case REQUEST_AUTHORIZATION: 290 | if (resultCode == RESULT_OK) { 291 | callAppsScriptTask(mLastFunctionCalled); 292 | } else { 293 | showErrorDialog(getString(R.string.no_auth_provided)); 294 | } 295 | break; 296 | } 297 | } 298 | 299 | /** 300 | * Call the API to execute an Apps Script function, after verifying 301 | * all the preconditions are satisfied. The preconditions are: Google 302 | * Play Services is installed, the device has a network connection, and 303 | * the Execution API service and credentials have been created. 304 | * @param functionToCall code indicating which function to call using 305 | * the API 306 | */ 307 | private void callAppsScriptTask(int functionToCall) { 308 | mLastFunctionCalled = functionToCall; 309 | if (! isGooglePlayServicesAvailable()) { 310 | toast(getString(R.string.gps_required)); 311 | acquireGooglePlayServices(); 312 | } else if (! mConnectionAvailable) { 313 | toast(getString(R.string.no_network)); 314 | } else if (! hasValidCredentials()) { 315 | createCredentialsAndService(); 316 | } else { 317 | switch (functionToCall) { 318 | case CALL_GET_TEXT: 319 | new GetTextTask().execute(mOrigLang, mDestLang, false); 320 | break; 321 | case CALL_TRANSLATE_TEXT: 322 | String originalText = mSelectedText.getText().toString(); 323 | new TranslateTextTask().execute( 324 | originalText, mOrigLang, mDestLang); 325 | break; 326 | case CALL_REPLACE_TEXT: 327 | String translation = mTranslationText.getText().toString(); 328 | new ReplaceTextTask().execute(translation); 329 | break; 330 | } 331 | } 332 | } 333 | 334 | /** 335 | * Attempts to initialize credentials and service object (prior to a call 336 | * to the API); uses the account provided by the calling app. This 337 | * requires the GET_ACCOUNTS permission to be explicitly granted by the 338 | * user; this will be requested here if it is not already granted. The 339 | * AfterPermissionGranted annotation indicates that this function will be 340 | * rerun automatically whenever the GET_ACCOUNTS permission is granted. 341 | */ 342 | @AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS) 343 | private void createCredentialsAndService() { 344 | if (EasyPermissions.hasPermissions( 345 | MainActivity.this, Manifest.permission.GET_ACCOUNTS)) { 346 | mCredential = GoogleAccountCredential.usingOAuth2( 347 | getApplicationContext(), Arrays.asList(SCOPES)) 348 | .setBackOff(new ExponentialBackOff()) 349 | .setSelectedAccountName(mAccount.name); 350 | mService = new com.google.api.services.script.Script.Builder( 351 | mTransport, mJsonFactory, setHttpTimeout(mCredential)) 352 | .setApplicationName(getString(R.string.app_name)) 353 | .build(); 354 | updateButtonEnableStatus(); 355 | 356 | // Callback to retry the API call with valid service/credentials 357 | callAppsScriptTask(mLastFunctionCalled); 358 | } else { 359 | // Request the GET_ACCOUNTS permission via a user dialog 360 | EasyPermissions.requestPermissions( 361 | MainActivity.this, 362 | getString(R.string.get_accounts_rationale), 363 | REQUEST_PERMISSION_GET_ACCOUNTS, 364 | Manifest.permission.GET_ACCOUNTS); 365 | } 366 | } 367 | 368 | /** 369 | * Returns true if a valid service object has been created and instantiated 370 | * with valid OAuth credentials; returns false otherwise. 371 | * @return true if the service and credentials are valid; false otherwise. 372 | */ 373 | private boolean hasValidCredentials() { 374 | return mService != null 375 | && mCredential != null 376 | && mCredential.getSelectedAccountName() != null; 377 | } 378 | 379 | /** 380 | * Respond to requests for permissions at runtime for SDK 23 and above. 381 | * @param requestCode The request code passed in 382 | * requestPermissions(android.app.Activity, String, int, String[]) 383 | * @param permissions The requested permissions. Never null. 384 | * @param grantResults The grant results for the corresponding permissions 385 | * which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null. 386 | */ 387 | @Override 388 | public void onRequestPermissionsResult(int requestCode, 389 | @NonNull String[] permissions, 390 | @NonNull int[] grantResults) { 391 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 392 | EasyPermissions.onRequestPermissionsResult( 393 | requestCode, permissions, grantResults, MainActivity.this); 394 | } 395 | 396 | /** 397 | * Callback for when a permission is granted using the EasyPermissions 398 | * library. 399 | * @param requestCode The request code associated with the requested 400 | * permission 401 | * @param list The requested permission list. Never null. 402 | */ 403 | @Override 404 | public void onPermissionsGranted(int requestCode, List list) { 405 | // Do nothing. 406 | } 407 | 408 | /** 409 | * Callback for when a permission is denied using the EasyPermissions 410 | * library. Displays status message and disables functionality that 411 | * would require that permission. 412 | * @param requestCode The request code associated with the requested 413 | * permission 414 | * @param list The requested permission list. Never null. 415 | */ 416 | @Override 417 | public void onPermissionsDenied(int requestCode, List list) { 418 | toast(getString(R.string.get_accounts_denied_message)); 419 | updateButtonEnableStatus(); 420 | } 421 | 422 | /** 423 | * Given the position of one of the language spinners, return the language 424 | * id corresponding to that position. 425 | * @param pos spinner position 426 | * @param omitAutoDetect true if the spinner does not include 'Auto-detect' 427 | * as the first option 428 | * @return String two-letter language id 429 | */ 430 | private String getLangIdFromSpinnerPosition(int pos, boolean omitAutoDetect) { 431 | String id; 432 | if (omitAutoDetect) { 433 | pos++; 434 | } 435 | switch (pos) { 436 | case 0: id = ""; break; // Auto-detect (input language only) 437 | case 1: id = "ar"; break; // Arabic 438 | case 2: id = "zh-CN"; break; // Chinese (Simplified) 439 | case 3: id = "en"; break; // English 440 | case 4: id = "fr"; break; // French 441 | case 5: id = "de"; break; // German 442 | case 6: id = "hi"; break; // Hindi 443 | case 7: id = "ja"; break; // Japanese 444 | case 8: id = "pt"; break; // Portuguese 445 | case 9: id = "es"; break; // Spanish 446 | default: id = "en"; break; 447 | } 448 | return id; 449 | } 450 | 451 | /** 452 | * Call the API to translate the selected text. 453 | */ 454 | private void translate() { 455 | String originalText = mSelectedText.getText().toString(); 456 | if (originalText.length() != 0) { 457 | callAppsScriptTask(CALL_TRANSLATE_TEXT); 458 | } 459 | } 460 | 461 | /** 462 | * Call the API to replace the translated text back to the original 463 | * document. 464 | * @param v The button's View context 465 | */ 466 | public void replace(View v) { 467 | String translation = mTranslationText.getText().toString(); 468 | if (translation.length() != 0) { 469 | callAppsScriptTask(CALL_REPLACE_TEXT); 470 | } 471 | } 472 | 473 | /** 474 | * Cancel the add-on and return without action to the calling app. 475 | * @param v The button's View context 476 | */ 477 | public void cancel(View v) { 478 | finishWithState(Activity.RESULT_CANCELED); 479 | } 480 | 481 | /** 482 | * End the add-on and return to the calling application. 483 | * @param state result code for add-on: one of Activity.RESULT_CANCELED or 484 | * Activity.RESULT_OK 485 | */ 486 | private void finishWithState(int state) { 487 | dismissProgressDialog(); 488 | setResult(state); 489 | finish(); 490 | } 491 | 492 | /** 493 | * Display a short toast message. 494 | * @param message text to display 495 | */ 496 | private void toast(String message) { 497 | Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); 498 | } 499 | 500 | /** 501 | * Checks whether the device currently has a network connection. 502 | * @return true if the device has a network connection, false otherwise 503 | */ 504 | private boolean isDeviceOnline() { 505 | ConnectivityManager connMgr = 506 | (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 507 | NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); 508 | return (networkInfo != null && networkInfo.isConnected()); 509 | } 510 | 511 | /** 512 | * Check that Google Play services APK is installed and up to date. 513 | * @return true if Google Play Services is available and up to 514 | * date on this device; false otherwise. 515 | */ 516 | private boolean isGooglePlayServicesAvailable() { 517 | GoogleApiAvailability apiAvailability = 518 | GoogleApiAvailability.getInstance(); 519 | final int connectionStatusCode = 520 | apiAvailability.isGooglePlayServicesAvailable(MainActivity.this); 521 | return connectionStatusCode == ConnectionResult.SUCCESS; 522 | } 523 | 524 | /** 525 | * Attempt to resolve a missing, out-of-date, invalid or disabled Google 526 | * Play Services installation via a user dialog, if possible. 527 | */ 528 | private void acquireGooglePlayServices() { 529 | GoogleApiAvailability apiAvailability = 530 | GoogleApiAvailability.getInstance(); 531 | final int connectionStatusCode = 532 | apiAvailability.isGooglePlayServicesAvailable(MainActivity.this); 533 | if (apiAvailability.isUserResolvableError(connectionStatusCode)) { 534 | showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode); 535 | } 536 | } 537 | /** 538 | * Display an error dialog showing that Google Play Services is missing 539 | * or out of date. 540 | * @param connectionStatusCode code describing the presence (or lack of) 541 | * Google Play Services on this device 542 | */ 543 | private void showGooglePlayServicesAvailabilityErrorDialog( 544 | final int connectionStatusCode) { 545 | Dialog dialog = 546 | GoogleApiAvailability.getInstance().getErrorDialog( 547 | MainActivity.this, 548 | connectionStatusCode, 549 | REQUEST_GOOGLE_PLAY_SERVICES); 550 | dialog.show(); 551 | } 552 | 553 | /** 554 | * Check the current connectivity status of the device and enable/disable 555 | * the highlight buttons if the device is online/offline, respectively. 556 | */ 557 | private void updateButtonEnableStatus() { 558 | mConnectionAvailable = isDeviceOnline(); 559 | boolean enable = mConnectionAvailable && hasValidCredentials(); 560 | mReplaceButton.setEnabled(enable); 561 | } 562 | 563 | /** 564 | * Show a dialog with an error message, with a button to cancel out of 565 | * the add-on. 566 | * @param errorMessage Error message to display 567 | */ 568 | protected void showErrorDialog(String errorMessage) { 569 | AlertDialog.Builder alertDialogBuilder = 570 | new AlertDialog.Builder(MainActivity.this); 571 | alertDialogBuilder.setTitle(getString(R.string.error_occurred)); 572 | alertDialogBuilder 573 | .setMessage(errorMessage) 574 | .setCancelable(false) 575 | .setNegativeButton( 576 | getString(R.string.exit_button), 577 | new DialogInterface.OnClickListener() { 578 | public void onClick(DialogInterface dialog, int id) { 579 | finishWithState(Activity.RESULT_CANCELED); 580 | } 581 | }); 582 | dismissProgressDialog(); 583 | AlertDialog alertDialog = alertDialogBuilder.create(); 584 | alertDialog.show(); 585 | } 586 | 587 | /** 588 | * Dismiss the ProgressDialog, if it is visible. 589 | */ 590 | public void dismissProgressDialog() { 591 | if (mProgress != null && mProgress.isShowing()) { 592 | Context context = 593 | ((ContextWrapper) mProgress.getContext()).getBaseContext(); 594 | // Dismiss only if launching activity hasn't been finished or 595 | // destroyed 596 | if(! (context instanceof Activity && 597 | ((Activity)context).isFinishing() || 598 | ((Activity)context).isDestroyed())) { 599 | mProgress.dismiss(); 600 | } 601 | } 602 | } 603 | 604 | /** 605 | * This BroadcastReceiver intercepts the 606 | * android.net.ConnectivityManager.CONNECTIVITY_ACTION, which indicates a 607 | * connection change. This is used to determine if the API can be called. 608 | */ 609 | public class NetworkReceiver extends BroadcastReceiver { 610 | /** 611 | * Responds to a connection change, recording whether a connection is 612 | * available. 613 | * @param context The Context in which the receiver is running 614 | * @param intent The Intent being received 615 | */ 616 | @Override 617 | public void onReceive(Context context, Intent intent) { 618 | // Checks the network connection. Based on the 619 | // result, enables/disables flag to allow API calls and 620 | // enables/disables buttons. 621 | updateButtonEnableStatus(); 622 | if (!mConnectionAvailable) { 623 | toast(getString(R.string.no_network)); 624 | } 625 | } 626 | } 627 | 628 | /** 629 | * Abstract class for handling Execution API calls. Typically a subclass of 630 | * this is created for each Apps Script function that will be called. 631 | * Placing the API calls in their own task ensures the UI stays responsive. 632 | */ 633 | public abstract class CallApiTask extends AsyncTask { 634 | 635 | private Exception mLastError = null; 636 | protected String mFunctionName; 637 | 638 | /** 639 | * Background task to call Apps Script API. 640 | * @param params Object parameters; used as parameters for that 641 | * function, in the given order 642 | * @return an object returned by the API; may be null 643 | */ 644 | @Override 645 | protected Object doInBackground(Object... params) { 646 | try { 647 | return executeCall(mFunctionName, Arrays.asList(params)); 648 | } catch (Exception e) { 649 | mLastError = e; 650 | cancel(true); 651 | return null; 652 | } 653 | } 654 | 655 | /** 656 | * Handle cancel requests -- specifically, those caused by exceptions 657 | * raised when attempting to call the API. 658 | */ 659 | @Override 660 | protected void onCancelled() { 661 | mProgress.hide(); 662 | if (mLastError != null) { 663 | if (mLastError instanceof GooglePlayServicesAvailabilityIOException) { 664 | showGooglePlayServicesAvailabilityErrorDialog( 665 | ((GooglePlayServicesAvailabilityIOException) mLastError).getConnectionStatusCode()); 666 | } else if (mLastError instanceof UserRecoverableAuthIOException) { 667 | startActivityForResult( 668 | ((UserRecoverableAuthIOException) mLastError).getIntent(), 669 | MainActivity.REQUEST_AUTHORIZATION); 670 | } else { 671 | showErrorDialog(mLastError.toString()); 672 | } 673 | } 674 | } 675 | 676 | /** 677 | * Interpret an error response returned by the API and return a String 678 | * summary. The summary will include the general error message and 679 | * (in most cases) a stack trace. 680 | * @param op the Operation returning an error response 681 | * @return summary of error response, or null if Operation returned no 682 | * error 683 | */ 684 | protected String getScriptError(Operation op) { 685 | if (op.getError() == null) { 686 | return null; 687 | } 688 | 689 | // Extract the first (and only) set of error details and cast as a 690 | // Map. The values of this map are the script's 'errorMessage' and 691 | // 'errorType', and an array of stack trace elements (which also 692 | // need to be cast as Maps). 693 | Map detail = op.getError().getDetails().get(0); 694 | List> stacktrace = 695 | (List>)detail.get("scriptStackTraceElements"); 696 | 697 | StringBuilder sb = 698 | new StringBuilder(getString(R.string.script_error)); 699 | sb.append(detail.get("errorMessage")); 700 | 701 | if (stacktrace != null) { 702 | // There may not be a stacktrace if the script didn't start 703 | // executing. 704 | sb.append(getString(R.string.script_error_trace)); 705 | for (Map elem : stacktrace) { 706 | sb.append("\n "); 707 | sb.append(elem.get("function")); 708 | sb.append(":"); 709 | sb.append(elem.get("lineNumber")); 710 | } 711 | } 712 | sb.append("\n"); 713 | return sb.toString(); 714 | } 715 | 716 | /** 717 | * Given a script function and a list of parameter objects, create a 718 | * request and run it with the API. 719 | * @param functionName script function name to call 720 | * @param params parameters needed by that function; may be null 721 | * @return Object returned from a successful execution; may be null 722 | * @throws IOException 723 | * @throws GoogleAuthException 724 | */ 725 | protected Object executeCall(String functionName, List params) 726 | throws IOException, GoogleAuthException { 727 | // Create execution request. 728 | ExecutionRequest request = new ExecutionRequest() 729 | .setFunction(functionName) 730 | .setSessionState(mState); 731 | if (params != null) { 732 | request.setParameters(params); 733 | } 734 | 735 | // Call the API and return the results (as an Operation object). 736 | Operation op = mService.scripts().run(SCRIPT_ID, request).execute(); 737 | 738 | // If the response from the API contains an error, throw an 739 | // exception to display it. 740 | if (op.getError() != null) { 741 | throw new IOException(getScriptError(op)); 742 | } 743 | 744 | // Return null if the API didn't yield a result. 745 | if (op.getResponse() == null || 746 | op.getResponse().get("result") == null) { 747 | return null; 748 | } 749 | 750 | return op.getResponse().get("result"); 751 | } 752 | } 753 | 754 | /** 755 | * An asynchronous task that handles the Apps Script API call to get 756 | * selected text data, and the translation of it to the previously chosen 757 | * destination language (or the default destination language). 758 | */ 759 | public class GetTextTask extends CallApiTask { 760 | /** 761 | * Clear the display and show the progress bar prior to calling the 762 | * API. 763 | */ 764 | @Override 765 | protected void onPreExecute() { 766 | mFunctionName = FUNCTION_GET_TEXT; 767 | mProgress.setMessage(getString(R.string.retrieve_status)); 768 | mProgress.show(); 769 | } 770 | 771 | /** 772 | * Take the text output and display it. If no data is returned, update 773 | * the status bar accordingly. 774 | * @param scriptResult object returned by the script function; may be 775 | * null 776 | */ 777 | @Override 778 | protected void onPostExecute(Object scriptResult) { 779 | mProgress.hide(); 780 | if (scriptResult != null) { 781 | // Place the original text and default translation into the UI. 782 | Map data = (Map) scriptResult; 783 | mSelectedText.setText(data.get("text")); 784 | mTranslationText.setText(data.get("translation")); 785 | } 786 | } 787 | } 788 | 789 | /** 790 | * An asynchronous task that handles the Apps Script API call to 791 | * translate text data. 792 | */ 793 | public class TranslateTextTask extends CallApiTask { 794 | /** 795 | * Clear the display and show the progress bar prior to calling the 796 | * API. 797 | */ 798 | @Override 799 | protected void onPreExecute() { 800 | mFunctionName = FUNCTION_TRANSLATE_TEXT; 801 | mProgress.setMessage(getString(R.string.translate_status)); 802 | mProgress.show(); 803 | } 804 | 805 | /** 806 | * Take the text output and display it. If no data is returned, update 807 | * the status bar accordingly. 808 | * @param scriptResult object returned by the script function; may be 809 | * null 810 | */ 811 | @Override 812 | protected void onPostExecute(Object scriptResult) { 813 | mProgress.hide(); 814 | if (scriptResult != null) { 815 | String data = (String) scriptResult; 816 | mTranslationText.setText(data); 817 | } 818 | } 819 | } 820 | 821 | /** 822 | * An asynchronous task that handles the Apps Script API call to send 823 | * (translated) text back into the document, replacing the previous 824 | * selection. 825 | */ 826 | public class ReplaceTextTask extends CallApiTask { 827 | /** 828 | * Show the progress bar prior to calling the API. 829 | */ 830 | @Override 831 | protected void onPreExecute() { 832 | mFunctionName = FUNCTION_INSERT_TEXT; 833 | mProgress.setMessage(getString(R.string.replace_status)); 834 | mProgress.show(); 835 | } 836 | 837 | /** 838 | * After finishing the API call, clear the progress bar and close 839 | * the add-on. 840 | * @param scriptResult object returned by the script function; should 841 | * always be null for this script function 842 | */ 843 | @Override 844 | protected void onPostExecute(Object scriptResult) { 845 | mProgress.hide(); 846 | finishWithState(Activity.RESULT_OK); 847 | } 848 | } 849 | } 850 | -------------------------------------------------------------------------------- /mobile-translate/app/src/main/res/layout-land/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 28 | 29 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 58 | 59 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /mobile-translate/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /mobile-translate/app/src/main/res/layout/buttons.xml: -------------------------------------------------------------------------------- 1 | 2 |