diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml
deleted file mode 100644
index 91c1a45dc..000000000
--- a/android/AndroidManifest.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/android/MyAddOnActivity.java b/android/MyAddOnActivity.java
deleted file mode 100644
index 25207d78d..000000000
--- a/android/MyAddOnActivity.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * Copyright Google LLC
- *
- * 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
- *
- * https://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.
- */
-// [START apps_script_android_activity]
-import android.accounts.Account;
-import android.app.Activity;
-
-public class MyAddOnActivity extends Activity {
- // Your activity...
-
- String sessionState;
- String docId;
- Account account;
-
- @Override
- protected void onCreate(Bundle state) {
- super.onCreate(state);
- docId = getIntent().getStringExtra(
- "com.google.android.apps.docs.addons.DocumentId");
- sessionState = getIntent().getStringExtra(
- "com.google.android.apps.docs.addons.SessionState");
- account = (Account) getIntent().getParcelableExtra(
- "com.google.android.apps.docs.addons.Account");
- // Your activity’s initialization...
- }
-
- // [START apps_script_android_execution]
- protected void makeRequest() {
- // Acquire the session state String from the calling Intent.
- sessionState = getIntent().getStringExtra(
- "com.google.android.apps.docs.addons.SessionState");
- // ...
- // Construct the API request.
- ExecutionRequest request = new ExecutionRequest()
- .setFunction(functionName)
- .setSessionState(sessionState)
- .setParameters(params) // Only needed if the function requires parameters
- .setDevMode(true); // Optional
- }
- // [END apps_script_android_execution]
-}
-// [END apps_script_android_activity]
\ No newline at end of file
diff --git a/android/README.md b/android/README.md
deleted file mode 100644
index bb9ebeb4c..000000000
--- a/android/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Apps Scripts for Android
-
-## [Converting Android Apps into Android Add-ons](https://developers.google.com/apps-script/add-ons/mobile/android)
-
-This sample describes how to convert an existing Android app into an Android add-on.
-
-## Mobile Doc Translate Add-on
-
-A sample Google Apps Script mobile add-on for Google Docs.
diff --git a/android/mobile-translate/Code.gs b/android/mobile-translate/Code.gs
deleted file mode 100644
index 3f147683e..000000000
--- a/android/mobile-translate/Code.gs
+++ /dev/null
@@ -1,260 +0,0 @@
-/**
- * Copyright Google LLC
- *
- * 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
- *
- * https://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.
- */
-/**
- * @OnlyCurrentDoc
- *
- * The above comment directs Apps Script to limit the scope of file
- * access for this add-on. It specifies that this add-on will only
- * attempt to read or modify the files in which the add-on is used,
- * and not all of the user's files. The authorization request message
- * presented to users will reflect this limited scope.
- */
-
-/**
- * Creates a menu entry in the Google Docs UI when the document is opened.
- * This method is only used by the regular add-on, and is never called by
- * the mobile add-on version.
- *
- * @param {object} e The event parameter for a simple onOpen trigger. To
- * determine which authorization mode (ScriptApp.AuthMode) the trigger is
- * running in, inspect e.authMode.
- */
-function onOpen(e) {
- DocumentApp.getUi().createAddonMenu()
- .addItem('Start', 'showSidebar')
- .addToUi();
-}
-
-/**
- * Runs when the add-on is installed.
- * This method is only used by the regular add-on, and is never called by
- * the mobile add-on version.
- *
- * @param {object} e The event parameter for a simple onInstall trigger. To
- * determine which authorization mode (ScriptApp.AuthMode) the trigger is
- * running in, inspect e.authMode. (In practice, onInstall triggers always
- * run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
- * AuthMode.NONE.)
- */
-function onInstall(e) {
- onOpen(e);
-}
-
-/**
- * Opens a sidebar in the document containing the add-on's user interface.
- * This method is only used by the regular add-on, and is never called by
- * the mobile add-on version.
- */
-function showSidebar() {
- var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
- .setTitle('Translate');
- DocumentApp.getUi().showSidebar(ui);
-}
-
-/**
- * Gets the text the user has selected. If there is no selection,
- * this function displays an error message.
- *
- * @return {Array.} The selected text.
- */
-function getSelectedText() {
- var selection = DocumentApp.getActiveDocument().getSelection();
- if (selection) {
- var text = [];
- var elements = selection.getSelectedElements();
- for (var i = 0; i < elements.length; i++) {
- if (elements[i].isPartial()) {
- var element = elements[i].getElement().asText();
- var startIndex = elements[i].getStartOffset();
- var endIndex = elements[i].getEndOffsetInclusive();
-
- text.push(element.getText().substring(startIndex, endIndex + 1));
- } else {
- var element = elements[i].getElement();
- // Only translate elements that can be edited as text; skip images and
- // other non-text elements.
- if (element.editAsText) {
- var elementText = element.asText().getText();
- // This check is necessary to exclude images, which return a blank
- // text element.
- if (elementText != '') {
- text.push(elementText);
- }
- }
- }
- }
- if (text.length) {
- throw new Error('Please select some text.');
- }
- return text;
- } else {
- throw new Error('Please select some text.');
- }
-}
-
-/**
- * Gets the stored user preferences for the origin and destination languages,
- * if they exist.
- * This method is only used by the regular add-on, and is never called by
- * the mobile add-on version.
- *
- * @return {Object} The user's origin and destination language preferences, if
- * they exist.
- */
-function getPreferences() {
- var userProperties = PropertiesService.getUserProperties();
- var languagePrefs = {
- originLang: userProperties.getProperty('originLang'),
- destLang: userProperties.getProperty('destLang')
- };
- return languagePrefs;
-}
-
-/**
- * Gets the user-selected text and translates it from the origin language to the
- * destination language. The languages are notated by their two-letter short
- * form. For example, English is 'en', and Spanish is 'es'. The origin language
- * may be specified as an empty string to indicate that Google Translate should
- * auto-detect the language.
- *
- * @param {string} origin The two-letter short form for the origin language.
- * @param {string} dest The two-letter short form for the destination language.
- * @param {boolean} savePrefs Whether to save the origin and destination
- * language preferences.
- * @return {Object} Object containing the original text and the result of the
- * translation.
- */
-function getTextAndTranslation(origin, dest, savePrefs) {
- var result = {};
- var text = getSelectedText();
- result['text'] = text.join('\n');
-
- if (savePrefs == true) {
- var userProperties = PropertiesService.getUserProperties();
- userProperties.setProperty('originLang', origin);
- userProperties.setProperty('destLang', dest);
- }
-
- result['translation'] = translateText(result['text'], origin, dest);
-
- return result;
-}
-
-/**
- * Replaces the text of the current selection with the provided text, or
- * inserts text at the current cursor location. (There will always be either
- * a selection or a cursor.) If multiple elements are selected, only inserts the
- * translated text in the first element that can contain text and removes the
- * other elements.
- *
- * @param {string} newText The text with which to replace the current selection.
- */
-function insertText(newText) {
- var selection = DocumentApp.getActiveDocument().getSelection();
- if (selection) {
- var replaced = false;
- var elements = selection.getSelectedElements();
- if (elements.length == 1 &&
- elements[0].getElement().getType() ==
- DocumentApp.ElementType.INLINE_IMAGE) {
- throw new Error('Can\'t insert text into an image.');
- }
- for (var i = 0; i < elements.length; i++) {
- if (elements[i].isPartial()) {
- var element = elements[i].getElement().asText();
- var startIndex = elements[i].getStartOffset();
- var endIndex = elements[i].getEndOffsetInclusive();
-
- var remainingText = element.getText().substring(endIndex + 1);
- element.deleteText(startIndex, endIndex);
- if (!replaced) {
- element.insertText(startIndex, newText);
- replaced = true;
- } else {
- // This block handles a selection that ends with a partial element. We
- // want to copy this partial text to the previous element so we don't
- // have a line-break before the last partial.
- var parent = element.getParent();
- parent.getPreviousSibling().asText().appendText(remainingText);
- // We cannot remove the last paragraph of a doc. If this is the case,
- // just remove the text within the last paragraph instead.
- if (parent.getNextSibling()) {
- parent.removeFromParent();
- } else {
- element.removeFromParent();
- }
- }
- } else {
- var element = elements[i].getElement();
- if (!replaced && element.editAsText) {
- // Only translate elements that can be edited as text, removing other
- // elements.
- element.clear();
- element.asText().setText(newText);
- replaced = true;
- } else {
- // We cannot remove the last paragraph of a doc. If this is the case,
- // just clear the element.
- if (element.getNextSibling()) {
- element.removeFromParent();
- } else {
- element.clear();
- }
- }
- }
- }
- } else {
- var cursor = DocumentApp.getActiveDocument().getCursor();
- var surroundingText = cursor.getSurroundingText().getText();
- var surroundingTextOffset = cursor.getSurroundingTextOffset();
-
- // If the cursor follows or preceds a non-space character, insert a space
- // between the character and the translation. Otherwise, just insert the
- // translation.
- if (surroundingTextOffset > 0) {
- if (surroundingText.charAt(surroundingTextOffset - 1) != ' ') {
- newText = ' ' + newText;
- }
- }
- if (surroundingTextOffset < surroundingText.length) {
- if (surroundingText.charAt(surroundingTextOffset) != ' ') {
- newText += ' ';
- }
- }
- cursor.insertText(newText);
- }
-}
-
-
-/**
- * Given text, translate it from the origin language to the destination
- * language. The languages are notated by their two-letter short form. For
- * example, English is 'en', and Spanish is 'es'. The origin language may be
- * specified as an empty string to indicate that Google Translate should
- * auto-detect the language.
- *
- * @param {string} text text to translate.
- * @param {string} origin The two-letter short form for the origin language.
- * @param {string} dest The two-letter short form for the destination language.
- * @return {string} The result of the translation, or the original text if
- * origin and dest languages are the same.
- */
-function translateText(text, origin, dest) {
- if (origin === dest) {
- return text;
- }
- return LanguageApp.translate(text, origin, dest);
-}
diff --git a/android/mobile-translate/README.md b/android/mobile-translate/README.md
deleted file mode 100644
index 83c245640..000000000
--- a/android/mobile-translate/README.md
+++ /dev/null
@@ -1,118 +0,0 @@
-Mobile Doc Translate Add-on
-===========================
-
-A sample Google Apps Script mobile add-on for Google Docs. This add-on is
-essentially a mobile version of the Docs
-[Translate Add-on Quickstart](https://developers.google.com/apps-script/quickstart/docs).
-
-Introduction
-------------
-
-Google Apps Script now allows developers to construct Mobile Add-ons -- Android
-applications which extend and support Google Docs and Sheets.
-
-This sample shows how to construct a mobile add-on called
-**Mobile Doc Translate**. This add-on allows users to select text in a
-Google Doc on their mobile device and see a translation of that text in one
-of several languages. The user can then edit the translation as needed and
-replace the original selected text in the Doc with the translation.
-
-
-Getting Started
----------------
-
-The add-on will need to call an Apps Script project to get Doc text, make
-translations, and insert text into the Doc. Users can access this add-on from
-the Google Docs Android app by highlighting text and selecting the add-on in the
-text context menu.
-
-The Apps Script code file for this project is `Code.gs`. This is the same code
-used in the [Translate Add-on Quickstart](https://developers.google.com/apps-script/quickstart/docs),
-but does not include the HTML code that defines the quickstart's sidebar.
-
-The mobile add-on will make use of the
-[Apps Script Execution API](https://developers.google.com/apps-script/guides/rest/)
-to call the `Code.gs` functions. The
-[Execution API quickstart for Android](https://developers.google.com/apps-script/guides/rest/quickstart/android)
-describes how to call Apps Script functions from Android applications.
-
-To build this sample:
-
-1. The `app/` folder in this repository contains all the required Android files
- for this add-on. These can be manually copied or imported into a new Android
- Studio project.
-1. Create a new Apps Script project.
-1. Replace the code in the new project's `Code.gs` file with the code from this
- repo.
-1. Save the project.
-1. In the code editor, select **Publish > Deploy as API** executable.
-1. In the dialog that opens, leave the **Version** as "New" and enter
- "Target-v1" into the text box. Click **Deploy**.
-1. Follow the
- [Keytool SHA1 Fingerprint](https://developers.google.com/apps-script/guides/rest/quickstart/android#step_1_acquire_a_sha1_fingerprint)
- instructions to acquire a SHA1 fingerprint for your project.
-1. Using that SHA code, follow the
- [Turn on the Execution API](https://developers.google.com/apps-script/guides/rest/quickstart/android#step_2_turn_on_the_api_name)
- instructions to enable the API for your script project and create OAuth
- credentials. Be sure to match the same package name used in your Android
- code.
-1. Edit the `MainActivity.java` file so that the `SCRIPT_ID` constant is set to
- your Apps Script project ID (in the script editor, select
- **File > Project properties**, and use the **Project key**).
-
- These steps should allow you to build the Android app and have it successfully
- call the Apps Script code. You can test it by:
-
- 1. Install the app on a test Android device.
- 1. Set the app as the debug app on the device by running this
- [ADB](https://developer.android.com/studio/command-line/adb.html)
- command:
- `$ adb shell am set-debug-app --persistent `
- 1. Open a docucment using the Google Docs app on the device.
- 1. Highlight some text in the doc and select the three-dot icon to open the
- context menu, and then select **Mobile Doc Translate**.
-
-Learn more
-----------
-
-To continue learning about mobile add-ons for Google Docs and Sheets,
-take a look at the following resources:
-
-* [Mobile Add-ons](https://developers.google.com/apps-script/add-ons/mobile)
-* [Apps Script Execution API](https://developers.google.com/apps-script/guides/)
-
-Support
--------
-
-For general Apps Script support, check the following:
-
-- Stack Overflow Tag: [google-apps-script](http://stackoverflow.com/questions/tagged/google-apps-script)
-- Issue Tracker: [google-apps-script-issues](https://code.google.com/p/google-apps-script-issues/issues/list)
-
-If you've found an error in this sample, please file an issue:
-https://github.com/googlesamples/apps-script-mobile-addons
-
-Patches are encouraged, and may be submitted by forking this project and
-submitting a pull request through GitHub.
-
-License
--------
-
-Copyright 2016 Google, Inc.
-
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements. See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership. The ASF licenses this file
-to you 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.
\ No newline at end of file
diff --git a/android/mobile-translate/app/build.gradle b/android/mobile-translate/app/build.gradle
deleted file mode 100644
index 5a3e94242..000000000
--- a/android/mobile-translate/app/build.gradle
+++ /dev/null
@@ -1,48 +0,0 @@
-apply plugin: 'com.android.application'
-
-def keystorePropertiesFile = rootProject.file("keystore.properties")
-def keystoreProperties = new Properties()
-keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
-
-android {
- signingConfigs {
- mainRelease {
- keyAlias keystoreProperties['keyAlias']
- keyPassword keystoreProperties['keyPassword']
- storeFile file(keystoreProperties['storeFile'])
- storePassword keystoreProperties['storePassword']
- }
- }
- compileSdkVersion 23
- buildToolsVersion "24.0.0 rc4"
- defaultConfig {
- applicationId "com.google.samples.mobiledoctranslate"
- minSdkVersion 17
- targetSdkVersion 23
- versionCode 2
- versionName "1.0.1"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'),
- 'proguard-rules.pro'
- signingConfig signingConfigs.mainRelease
- }
- }
-}
-
-dependencies {
- compile fileTree(include: ['*.jar'], dir: 'libs')
- testCompile 'junit:junit:4.12'
- compile 'com.android.support:appcompat-v7:23.4.0'
- compile 'com.google.android.gms:play-services-auth:9.0.2'
- compile 'com.android.support:cardview-v7:23.4.0'
- compile 'pub.devrel:easypermissions:0.1.5'
- compile('com.google.api-client:google-api-client-android:1.20.0') {
- exclude group: 'org.apache.httpcomponents'
- }
- compile('com.google.apis:google-api-services-script:v1-rev1-1.20.0') {
- exclude group: 'org.apache.httpcomponents'
- }
-}
diff --git a/android/mobile-translate/app/src/main/AndroidManifest.xml b/android/mobile-translate/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 6bffeaec7..000000000
--- a/android/mobile-translate/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/DefaultLaunchActivity.java b/android/mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/DefaultLaunchActivity.java
deleted file mode 100644
index e5dd3bfb9..000000000
--- a/android/mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/DefaultLaunchActivity.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Copyright Google LLC
- *
- * 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
- *
- * https://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.google.samples.mobiledoctranslate;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-
-/**
- * Since this add-on needs context from the Docs editor app, it should only be
- * launched from that app (via context menus).
- *
- * This activity handles the edge case where the app is (erroneously) launched
- * from the home screen or a notification. This activity simply presents a
- * message to the user and provides an Exit button.
- */
-public class DefaultLaunchActivity extends Activity {
-
- /**
- * Create the default launch activity.
- * @param savedInstanceState previously saved instance data
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.launch_default);
- }
-
- /**
- * Cancel the add-on and return without action.
- * @param v The button's View context
- */
- public void cancel(View v) {
- setResult(Activity.RESULT_CANCELED);
- finish();
- }
-}
diff --git a/android/mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/MainActivity.java b/android/mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/MainActivity.java
deleted file mode 100644
index 1995695aa..000000000
--- a/android/mobile-translate/app/src/main/java/com/google/samples/mobiledoctranslate/MainActivity.java
+++ /dev/null
@@ -1,848 +0,0 @@
-/**
- * Copyright Google LLC
- *
- * 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
- *
- * https://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.google.samples.mobiledoctranslate;
-
-import com.google.android.gms.auth.GoogleAuthException;
-import com.google.android.gms.common.ConnectionResult;
-import com.google.android.gms.common.GoogleApiAvailability;
-import com.google.api.client.extensions.android.http.AndroidHttp;
-import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
-import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
-import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
-import com.google.api.client.http.HttpRequest;
-import com.google.api.client.http.HttpRequestInitializer;
-import com.google.api.client.http.HttpTransport;
-import com.google.api.client.json.JsonFactory;
-import com.google.api.client.json.jackson2.JacksonFactory;
-import com.google.api.client.util.ExponentialBackOff;
-import com.google.api.services.script.model.*;
-import com.google.api.services.script.Script;
-
-import android.Manifest;
-import android.accounts.Account;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.text.method.ScrollingMovementMethod;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.io.IOException;
-import java.lang.StringBuilder;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import pub.devrel.easypermissions.AfterPermissionGranted;
-import pub.devrel.easypermissions.EasyPermissions;
-
-/**
- * This is the main (and only) activity of the add-on. It shows the user what
- * text or cells were selected, the results of translation and provides some
- * UI controls.
- */
-public class MainActivity extends Activity
- implements EasyPermissions.PermissionCallbacks {
-
- /**
- * The script ID for the Apps Script the add-on will call
- */
- private static final String SCRIPT_ID = "ENTER_YOUR_SCRIPT_ID_HERE";
-
- // Constants
- private static final String FUNCTION_GET_TEXT = "getTextAndTranslation";
- private static final String FUNCTION_TRANSLATE_TEXT = "translateText";
- private static final String FUNCTION_INSERT_TEXT = "insertText";
- static final int REQUEST_AUTHORIZATION = 1001;
- static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
- static final int REQUEST_PERMISSION_GET_ACCOUNTS = 1003;
- private static final String[] SCOPES = {
- "https://www.googleapis.com/auth/documents.currentonly",
- "https://www.googleapis.com/auth/script.scriptapp",
- "https://www.googleapis.com/auth/script.storage"
- };
- static final String SAVED_ORIG_LANG = "origLangPosition";
- static final String SAVED_DEST_LANG = "destLangPosition";
- private static final int CALL_GET_TEXT = 0;
- private static final int CALL_TRANSLATE_TEXT = 1;
- private static final int CALL_REPLACE_TEXT = 2;
-
- /**
- * An Apps Script API service object used to access the API, and related
- * objects
- */
- Script mService = null;
- GoogleAccountCredential mCredential = null;
- final HttpTransport mTransport = AndroidHttp.newCompatibleTransport();
- final JsonFactory mJsonFactory = JacksonFactory.getDefaultInstance();
-
- // Layout components
- private TextView mSelectedText;
- private EditText mTranslationText;
- private Button mReplaceButton;
- private ProgressDialog mProgress;
-
- // Translation language controls
- private String mOrigLang;
- private String mDestLang;
- private int mPrevOrigSpinnerPos;
- private int mPrevDestSpinnerPos;
-
- // Other variables
- private NetworkReceiver mReceiver;
- private boolean mConnectionAvailable;
- private int mLastFunctionCalled;
- private String mState;
- private Account mAccount;
-
- /**
- * Create the main activity.
- * @param savedInstanceState previously saved instance data
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- // Verify the add-on was called from the Docs editor.
- if (! "com.google.android.apps.docs.editors.docs".equals(
- getCallingPackage())) {
- showErrorDialog(getString(R.string.unexpected_app)
- + getCallingPackage());
- }
-
- // Acquire the doc/sheet state from the incoming intent.
- // It's also possible to acquire the docId from the intent;
- // that is not used in this example, however.
- mState = getIntent().getStringExtra(
- "com.google.android.apps.docs.addons.SessionState");
- mAccount = getIntent().getParcelableExtra(
- "com.google.android.apps.docs.addons.Account");
-
- // Load previously chosen language selections, if any
- SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
- mPrevOrigSpinnerPos = settings.getInt(SAVED_ORIG_LANG, 0);
- mOrigLang = getLangIdFromSpinnerPosition(mPrevOrigSpinnerPos, false);
- mPrevDestSpinnerPos = settings.getInt(SAVED_DEST_LANG, 0);
- mDestLang = getLangIdFromSpinnerPosition(mPrevDestSpinnerPos, true);
-
- // Initialize layout objects
- mProgress = new ProgressDialog(MainActivity.this);
-
- mSelectedText = (TextView) findViewById(R.id.selected_text);
- mSelectedText.setVerticalScrollBarEnabled(true);
- mSelectedText.setMovementMethod(new ScrollingMovementMethod());
-
- mTranslationText = (EditText) findViewById(R.id.translated_text);
- mTranslationText.setVerticalScrollBarEnabled(true);
- mTranslationText.setMovementMethod(new ScrollingMovementMethod());
-
- mReplaceButton = (Button) findViewById(R.id.replace_button);
-
- Spinner origLangSpinner = (Spinner) findViewById(R.id.origin_lang);
- Spinner destLangSpinner = (Spinner) findViewById(R.id.dest_lang);
- origLangSpinner.setSelection(mPrevOrigSpinnerPos);
- destLangSpinner.setSelection(mPrevDestSpinnerPos);
- origLangSpinner.setOnItemSelectedListener(
- new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(
- AdapterView> parent, View view, int pos, long id) {
- if (pos != mPrevOrigSpinnerPos) {
- mPrevOrigSpinnerPos = pos;
- mOrigLang = getLangIdFromSpinnerPosition(pos, false);
- SharedPreferences settings =
- getPreferences(Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = settings.edit();
- editor.putInt(SAVED_ORIG_LANG, pos);
- editor.apply();
- translate();
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {}
- });
- destLangSpinner.setOnItemSelectedListener(
- new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(
- AdapterView> parent, View view, int pos, long id) {
- if (pos != mPrevDestSpinnerPos) {
- mPrevDestSpinnerPos = pos;
- mDestLang = getLangIdFromSpinnerPosition(pos, true);
- SharedPreferences settings =
- getPreferences(Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = settings.edit();
- editor.putInt(SAVED_DEST_LANG, pos);
- editor.apply();
- translate();
- }
- }
-
- @Override
- public void onNothingSelected(AdapterView> parent) {}
- });
-
- // Register BroadcastReceiver to track connection changes, and
- // determine if a connection is initially available
- IntentFilter filter =
- new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
- mReceiver = new NetworkReceiver();
- MainActivity.this.registerReceiver(mReceiver, filter);
- updateButtonEnableStatus();
-
- // Start the add-on by attempting to retrieve the selected text from
- // the Doc that fired the add-on
- callAppsScriptTask(CALL_GET_TEXT);
- }
-
- /**
- * Extend the given HttpRequestInitializer (usually a Credentials object)
- * with additional initialize() instructions.
- *
- * @param requestInitializer the initializer to copy and adjust; typically
- * a Credential object
- * @return an initializer with an extended read timeout
- */
- private static HttpRequestInitializer setHttpTimeout(
- final HttpRequestInitializer requestInitializer) {
- return new HttpRequestInitializer() {
- @Override
- public void initialize(HttpRequest httpRequest)
- throws java.io.IOException {
- requestInitializer.initialize(httpRequest);
- // This allows the API to call (and avoid timing out on)
- // functions that take up to 30 seconds to complete. Note that
- // the maximum allowed script run time is 6 minutes.
- httpRequest.setReadTimeout(30000);
- }
- };
- }
-
- /**
- * Clean up and destroy the main activity.
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Unregister the connectivity broadcast receiver.
- if (mReceiver != null) {
- MainActivity.this.unregisterReceiver(mReceiver);
- }
- }
-
- /**
- * Called when an activity launched here (specifically, AccountPicker
- * and authorization) exits, giving you the requestCode you started it with,
- * the resultCode it returned, and any additional data from it.
- * @param requestCode code indicating which activity result is incoming
- * @param resultCode code indicating the result of the incoming
- * activity result
- * @param data Intent (containing result data) returned by incoming
- * activity result
- */
- @Override
- protected void onActivityResult(
- int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- switch(requestCode) {
- case REQUEST_GOOGLE_PLAY_SERVICES:
- if (resultCode == RESULT_OK) {
- callAppsScriptTask(mLastFunctionCalled);
- } else {
- showErrorDialog(getString(R.string.gps_required));
- }
- break;
- case REQUEST_AUTHORIZATION:
- if (resultCode == RESULT_OK) {
- callAppsScriptTask(mLastFunctionCalled);
- } else {
- showErrorDialog(getString(R.string.no_auth_provided));
- }
- break;
- }
- }
-
- /**
- * Call the API to execute an Apps Script function, after verifying
- * all the preconditions are satisfied. The preconditions are: Google
- * Play Services is installed, the device has a network connection, and
- * the Execution API service and credentials have been created.
- * @param functionToCall code indicating which function to call using
- * the API
- */
- private void callAppsScriptTask(int functionToCall) {
- mLastFunctionCalled = functionToCall;
- if (! isGooglePlayServicesAvailable()) {
- toast(getString(R.string.gps_required));
- acquireGooglePlayServices();
- } else if (! mConnectionAvailable) {
- toast(getString(R.string.no_network));
- } else if (! hasValidCredentials()) {
- createCredentialsAndService();
- } else {
- switch (functionToCall) {
- case CALL_GET_TEXT:
- new GetTextTask().execute(mOrigLang, mDestLang, false);
- break;
- case CALL_TRANSLATE_TEXT:
- String originalText = mSelectedText.getText().toString();
- new TranslateTextTask().execute(
- originalText, mOrigLang, mDestLang);
- break;
- case CALL_REPLACE_TEXT:
- String translation = mTranslationText.getText().toString();
- new ReplaceTextTask().execute(translation);
- break;
- }
- }
- }
-
- /**
- * Attempts to initialize credentials and service object (prior to a call
- * to the API); uses the account provided by the calling app. This
- * requires the GET_ACCOUNTS permission to be explicitly granted by the
- * user; this will be requested here if it is not already granted. The
- * AfterPermissionGranted annotation indicates that this function will be
- * rerun automatically whenever the GET_ACCOUNTS permission is granted.
- */
- @AfterPermissionGranted(REQUEST_PERMISSION_GET_ACCOUNTS)
- private void createCredentialsAndService() {
- if (EasyPermissions.hasPermissions(
- MainActivity.this, Manifest.permission.GET_ACCOUNTS)) {
- mCredential = GoogleAccountCredential.usingOAuth2(
- getApplicationContext(), Arrays.asList(SCOPES))
- .setBackOff(new ExponentialBackOff())
- .setSelectedAccountName(mAccount.name);
- mService = new com.google.api.services.script.Script.Builder(
- mTransport, mJsonFactory, setHttpTimeout(mCredential))
- .setApplicationName(getString(R.string.app_name))
- .build();
- updateButtonEnableStatus();
-
- // Callback to retry the API call with valid service/credentials
- callAppsScriptTask(mLastFunctionCalled);
- } else {
- // Request the GET_ACCOUNTS permission via a user dialog
- EasyPermissions.requestPermissions(
- MainActivity.this,
- getString(R.string.get_accounts_rationale),
- REQUEST_PERMISSION_GET_ACCOUNTS,
- Manifest.permission.GET_ACCOUNTS);
- }
- }
-
- /**
- * Returns true if a valid service object has been created and instantiated
- * with valid OAuth credentials; returns false otherwise.
- * @return true if the service and credentials are valid; false otherwise.
- */
- private boolean hasValidCredentials() {
- return mService != null
- && mCredential != null
- && mCredential.getSelectedAccountName() != null;
- }
-
- /**
- * Respond to requests for permissions at runtime for SDK 23 and above.
- * @param requestCode The request code passed in
- * requestPermissions(android.app.Activity, String, int, String[])
- * @param permissions The requested permissions. Never null.
- * @param grantResults The grant results for the corresponding permissions
- * which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
- */
- @Override
- public void onRequestPermissionsResult(int requestCode,
- @NonNull String[] permissions,
- @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- EasyPermissions.onRequestPermissionsResult(
- requestCode, permissions, grantResults, MainActivity.this);
- }
-
- /**
- * Callback for when a permission is granted using the EasyPermissions
- * library.
- * @param requestCode The request code associated with the requested
- * permission
- * @param list The requested permission list. Never null.
- */
- @Override
- public void onPermissionsGranted(int requestCode, List list) {
- // Do nothing.
- }
-
- /**
- * Callback for when a permission is denied using the EasyPermissions
- * library. Displays status message and disables functionality that
- * would require that permission.
- * @param requestCode The request code associated with the requested
- * permission
- * @param list The requested permission list. Never null.
- */
- @Override
- public void onPermissionsDenied(int requestCode, List list) {
- toast(getString(R.string.get_accounts_denied_message));
- updateButtonEnableStatus();
- }
-
- /**
- * Given the position of one of the language spinners, return the language
- * id corresponding to that position.
- * @param pos spinner position
- * @param omitAutoDetect true if the spinner does not include 'Auto-detect'
- * as the first option
- * @return String two-letter language id
- */
- private String getLangIdFromSpinnerPosition(int pos, boolean omitAutoDetect) {
- String id;
- if (omitAutoDetect) {
- pos++;
- }
- switch (pos) {
- case 0: id = ""; break; // Auto-detect (input language only)
- case 1: id = "ar"; break; // Arabic
- case 2: id = "zh-CN"; break; // Chinese (Simplified)
- case 3: id = "en"; break; // English
- case 4: id = "fr"; break; // French
- case 5: id = "de"; break; // German
- case 6: id = "hi"; break; // Hindi
- case 7: id = "ja"; break; // Japanese
- case 8: id = "pt"; break; // Portuguese
- case 9: id = "es"; break; // Spanish
- default: id = "en"; break;
- }
- return id;
- }
-
- /**
- * Call the API to translate the selected text.
- */
- private void translate() {
- String originalText = mSelectedText.getText().toString();
- if (originalText.length() != 0) {
- callAppsScriptTask(CALL_TRANSLATE_TEXT);
- }
- }
-
- /**
- * Call the API to replace the translated text back to the original
- * document.
- * @param v The button's View context
- */
- public void replace(View v) {
- String translation = mTranslationText.getText().toString();
- if (translation.length() != 0) {
- callAppsScriptTask(CALL_REPLACE_TEXT);
- }
- }
-
- /**
- * Cancel the add-on and return without action to the calling app.
- * @param v The button's View context
- */
- public void cancel(View v) {
- finishWithState(Activity.RESULT_CANCELED);
- }
-
- /**
- * End the add-on and return to the calling application.
- * @param state result code for add-on: one of Activity.RESULT_CANCELED or
- * Activity.RESULT_OK
- */
- private void finishWithState(int state) {
- dismissProgressDialog();
- setResult(state);
- finish();
- }
-
- /**
- * Display a short toast message.
- * @param message text to display
- */
- private void toast(String message) {
- Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
- }
-
- /**
- * Checks whether the device currently has a network connection.
- * @return true if the device has a network connection, false otherwise
- */
- private boolean isDeviceOnline() {
- ConnectivityManager connMgr =
- (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
- return (networkInfo != null && networkInfo.isConnected());
- }
-
- /**
- * Check that Google Play services APK is installed and up to date.
- * @return true if Google Play Services is available and up to
- * date on this device; false otherwise.
- */
- private boolean isGooglePlayServicesAvailable() {
- GoogleApiAvailability apiAvailability =
- GoogleApiAvailability.getInstance();
- final int connectionStatusCode =
- apiAvailability.isGooglePlayServicesAvailable(MainActivity.this);
- return connectionStatusCode == ConnectionResult.SUCCESS;
- }
-
- /**
- * Attempt to resolve a missing, out-of-date, invalid or disabled Google
- * Play Services installation via a user dialog, if possible.
- */
- private void acquireGooglePlayServices() {
- GoogleApiAvailability apiAvailability =
- GoogleApiAvailability.getInstance();
- final int connectionStatusCode =
- apiAvailability.isGooglePlayServicesAvailable(MainActivity.this);
- if (apiAvailability.isUserResolvableError(connectionStatusCode)) {
- showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
- }
- }
- /**
- * Display an error dialog showing that Google Play Services is missing
- * or out of date.
- * @param connectionStatusCode code describing the presence (or lack of)
- * Google Play Services on this device
- */
- private void showGooglePlayServicesAvailabilityErrorDialog(
- final int connectionStatusCode) {
- Dialog dialog =
- GoogleApiAvailability.getInstance().getErrorDialog(
- MainActivity.this,
- connectionStatusCode,
- REQUEST_GOOGLE_PLAY_SERVICES);
- dialog.show();
- }
-
- /**
- * Check the current connectivity status of the device and enable/disable
- * the highlight buttons if the device is online/offline, respectively.
- */
- private void updateButtonEnableStatus() {
- mConnectionAvailable = isDeviceOnline();
- boolean enable = mConnectionAvailable && hasValidCredentials();
- mReplaceButton.setEnabled(enable);
- }
-
- /**
- * Show a dialog with an error message, with a button to cancel out of
- * the add-on.
- * @param errorMessage Error message to display
- */
- protected void showErrorDialog(String errorMessage) {
- AlertDialog.Builder alertDialogBuilder =
- new AlertDialog.Builder(MainActivity.this);
- alertDialogBuilder.setTitle(getString(R.string.error_occurred));
- alertDialogBuilder
- .setMessage(errorMessage)
- .setCancelable(false)
- .setNegativeButton(
- getString(R.string.exit_button),
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int id) {
- finishWithState(Activity.RESULT_CANCELED);
- }
- });
- dismissProgressDialog();
- AlertDialog alertDialog = alertDialogBuilder.create();
- alertDialog.show();
- }
-
- /**
- * Dismiss the ProgressDialog, if it is visible.
- */
- public void dismissProgressDialog() {
- if (mProgress != null && mProgress.isShowing()) {
- Context context =
- ((ContextWrapper) mProgress.getContext()).getBaseContext();
- // Dismiss only if launching activity hasn't been finished or
- // destroyed
- if(! (context instanceof Activity &&
- ((Activity)context).isFinishing() ||
- ((Activity)context).isDestroyed())) {
- mProgress.dismiss();
- }
- }
- }
-
- /**
- * This BroadcastReceiver intercepts the
- * android.net.ConnectivityManager.CONNECTIVITY_ACTION, which indicates a
- * connection change. This is used to determine if the API can be called.
- */
- public class NetworkReceiver extends BroadcastReceiver {
- /**
- * Responds to a connection change, recording whether a connection is
- * available.
- * @param context The Context in which the receiver is running
- * @param intent The Intent being received
- */
- @Override
- public void onReceive(Context context, Intent intent) {
- // Checks the network connection. Based on the
- // result, enables/disables flag to allow API calls and
- // enables/disables buttons.
- updateButtonEnableStatus();
- if (!mConnectionAvailable) {
- toast(getString(R.string.no_network));
- }
- }
- }
-
- /**
- * Abstract class for handling Execution API calls. Typically a subclass of
- * this is created for each Apps Script function that will be called.
- * Placing the API calls in their own task ensures the UI stays responsive.
- */
- public abstract class CallApiTask extends AsyncTask