diff --git a/.gitignore b/.gitignore index cd81313..ec870c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ -/.classpath -/.idea/libraries +.gradle +/local.properties /.idea/workspace.xml -/.pmd -/.project -/.svn/ -/bin -/gen -/proguard -/project.properties \ No newline at end of file +/.idea/libraries +.DS_Store +/build +/captures diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..249f321 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Emaily \ No newline at end of file diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..4c5388c --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,201 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..9a8b7e5 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..8d2df47 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..ccc2a99 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,37 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b0c0cbc --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ba50301 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index b080d2d..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/Emaily.iml b/Emaily.iml new file mode 100644 index 0000000..5a0419d --- /dev/null +++ b/Emaily.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 0000000..f184335 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..074f329 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 22 + buildToolsVersion "21.1.2" + + defaultConfig { + applicationId "net.thauvin.erik.android.emaily" + minSdkVersion 11 + targetSdkVersion 22 + versionCode 2 + versionName "1.1b9" + } + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + //compile 'com.android.support:appcompat-v7:22.0.0' +} diff --git a/lib/bitlyj-2.0.0.jar b/app/libs/bitlyj-2.0.0.jar similarity index 100% rename from lib/bitlyj-2.0.0.jar rename to app/libs/bitlyj-2.0.0.jar diff --git a/lib/google-api-client-1.7.0-beta.jar b/app/libs/google-api-client-1.7.0-beta.jar similarity index 100% rename from lib/google-api-client-1.7.0-beta.jar rename to app/libs/google-api-client-1.7.0-beta.jar diff --git a/lib/google-api-client-android2-1.7.0-beta.jar b/app/libs/google-api-client-android2-1.7.0-beta.jar similarity index 100% rename from lib/google-api-client-android2-1.7.0-beta.jar rename to app/libs/google-api-client-android2-1.7.0-beta.jar diff --git a/lib/google-api-urlshortener-v1-rev2-java-1.4.0-beta.jar b/app/libs/google-api-urlshortener-v1-rev2-java-1.4.0-beta.jar similarity index 100% rename from lib/google-api-urlshortener-v1-rev2-java-1.4.0-beta.jar rename to app/libs/google-api-urlshortener-v1-rev2-java-1.4.0-beta.jar diff --git a/lib/google-http-client-1.7.0-beta.jar b/app/libs/google-http-client-1.7.0-beta.jar similarity index 100% rename from lib/google-http-client-1.7.0-beta.jar rename to app/libs/google-http-client-1.7.0-beta.jar diff --git a/lib/google-http-client-android2-1.7.0-beta.jar b/app/libs/google-http-client-android2-1.7.0-beta.jar similarity index 100% rename from lib/google-http-client-android2-1.7.0-beta.jar rename to app/libs/google-http-client-android2-1.7.0-beta.jar diff --git a/lib/google-http-client-android3-1.7.0-beta.jar b/app/libs/google-http-client-android3-1.7.0-beta.jar similarity index 100% rename from lib/google-http-client-android3-1.7.0-beta.jar rename to app/libs/google-http-client-android3-1.7.0-beta.jar diff --git a/lib/google-oauth-client-1.7.0-beta.jar b/app/libs/google-oauth-client-1.7.0-beta.jar similarity index 100% rename from lib/google-oauth-client-1.7.0-beta.jar rename to app/libs/google-oauth-client-1.7.0-beta.jar diff --git a/lib/gson-2.1.jar b/app/libs/gson-2.1.jar similarity index 100% rename from lib/gson-2.1.jar rename to app/libs/gson-2.1.jar diff --git a/lib/guava-11.0.1.jar b/app/libs/guava-11.0.1.jar similarity index 100% rename from lib/guava-11.0.1.jar rename to app/libs/guava-11.0.1.jar diff --git a/lib/jackson-core-asl-1.9.4.jar b/app/libs/jackson-core-asl-1.9.4.jar similarity index 100% rename from lib/jackson-core-asl-1.9.4.jar rename to app/libs/jackson-core-asl-1.9.4.jar diff --git a/lib/jsr305-1.3.9.jar b/app/libs/jsr305-1.3.9.jar similarity index 100% rename from lib/jsr305-1.3.9.jar rename to app/libs/jsr305-1.3.9.jar diff --git a/lib/protobuf-java-2.2.0.jar b/app/libs/protobuf-java-2.2.0.jar similarity index 100% rename from lib/protobuf-java-2.2.0.jar rename to app/libs/protobuf-java-2.2.0.jar diff --git a/proguard.cfg b/app/proguard-rules.pro similarity index 74% rename from proguard.cfg rename to app/proguard-rules.pro index 1d0eecd..e22f608 100644 --- a/proguard.cfg +++ b/app/proguard-rules.pro @@ -1,3 +1,20 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\erik\AppData\Local\Android\android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} -optimizationpasses 5 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses diff --git a/AndroidManifest.xml b/app/src/main/AndroidManifest.xml similarity index 77% rename from AndroidManifest.xml rename to app/src/main/AndroidManifest.xml index 420cd31..7a79e88 100644 --- a/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,12 +1,6 @@ - - + package="net.thauvin.erik.android.emaily"> @@ -14,13 +8,13 @@ + android:theme="@style/Emaily"> + android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"> @@ -31,7 +25,7 @@ + android:label="@string/app_name"> diff --git a/src/net/thauvin/erik/android/emaily/BitlyCredsDialog.java b/app/src/main/java/net/thauvin/erik/android/emaily/BitlyCredsDialog.java similarity index 99% rename from src/net/thauvin/erik/android/emaily/BitlyCredsDialog.java rename to app/src/main/java/net/thauvin/erik/android/emaily/BitlyCredsDialog.java index 71fa510..c098b24 100644 --- a/src/net/thauvin/erik/android/emaily/BitlyCredsDialog.java +++ b/app/src/main/java/net/thauvin/erik/android/emaily/BitlyCredsDialog.java @@ -87,7 +87,7 @@ public class BitlyCredsDialog extends DialogPreference { final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mContext.getString(R.string.prefs_bitly_creds_url))); mContext.startActivity(intent); - }; + } }); } diff --git a/app/src/main/java/net/thauvin/erik/android/emaily/Emaily.java b/app/src/main/java/net/thauvin/erik/android/emaily/Emaily.java new file mode 100644 index 0000000..6f3f71e --- /dev/null +++ b/app/src/main/java/net/thauvin/erik/android/emaily/Emaily.java @@ -0,0 +1,699 @@ +/* + * @(#)Emaily.java + * + * Copyright (c) 2011-2012 Erik C. Thauvin (http://erik.thauvin.net/) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the authors nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id$ + * + */ +package net.thauvin.erik.android.emaily; + +import static com.rosaloves.bitlyj.Bitly.as; +import static com.rosaloves.bitlyj.Bitly.shorten; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Date; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.ClipboardManager; +import android.text.Html; +import android.util.Log; +import android.widget.Toast; + +import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager; +import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.http.json.JsonHttpRequest; +import com.google.api.client.http.json.JsonHttpRequestInitializer; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson.JacksonFactory; +import com.google.api.services.urlshortener.Urlshortener; +import com.google.api.services.urlshortener.UrlshortenerRequest; +import com.google.api.services.urlshortener.model.Url; + +/** + * The Emaily class implements a URL shortener intent. + * + * @author Erik C. Thauvin + * @version $Revision$ + * @created Oct 11, 2011 + * @since 1.0 + */ +@SuppressWarnings("deprecation") +public class Emaily extends Activity +{ + private static final String ACCOUNT_TYPE = "com.google"; + private static final String OAUTH_URL = "oauth2:https://www.googleapis.com/auth/urlshortener"; + + private String appName; + private SharedPreferences sharedPrefs; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + final Intent intent = getIntent(); + + appName = getString(R.string.app_name); + sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + + if (Intent.ACTION_SEND.equals(intent.getAction())) + { + final boolean isGoogl = getBoolPref(R.string.prefs_key_googl_enabled, true); + + if (isGoogl) + { + final String account = getPref(R.string.prefs_key_googl_account); + + if (isValid(account)) + { + startEmailyTask(intent, new Account(account, ACCOUNT_TYPE), false); + } + else + { + final AlertDialog.Builder builder = new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT); + builder.setTitle(R.string.dialog_accounts_title); + + final Account[] accounts = AccountManager.get(this).getAccountsByType(ACCOUNT_TYPE); + final int size = accounts.length; + if (size > 0) + { + if (size == 1) + { + startEmailyTask(intent, accounts[0], false); + } + else + { + final CharSequence[] names = new CharSequence[size]; + for (int i = 0; i < size; i++) + { + names[i] = accounts[i].name; + } + + builder.setSingleChoiceItems(names, 0, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + + final Editor editor = sharedPrefs.edit(); + editor.putString(getString(R.string.prefs_key_googl_account), names[which].toString()); + editor.putLong(getString(R.string.prefs_key_googl_token_expiry), 0L); + editor.commit(); + + startEmailyTask(intent, accounts[which], false); + } + + }); + + builder.create().show(); + } + } + else + { + startEmailyTask(intent, isGoogl, false); + } + } + } + else + { + startEmailyTask(intent, isGoogl, false); + } + } + else + { + Emaily.this.finish(); + } + + } + + /** + * Starts the task. + * + * @param intent The original intent. + * @param isGoogl The goo.gl flag. + * @param isRetry The retry flag. + */ + private void startEmailyTask(final Intent intent, final boolean isGoogl, final boolean isRetry) + { + final EmailyTask task; + + if (isGoogl) + { + task = new EmailyTask(getPref(R.string.prefs_key_googl_account), getPref(R.string.prefs_key_googl_token), isGoogl, + getBoolPref(R.string.prefs_key_html_chkbox), isRetry); + } + else + { + task = new EmailyTask(getPref(R.string.prefs_key_bitly_username), getPref(R.string.prefs_key_bitly_apikey), isGoogl, + getBoolPref(R.string.prefs_key_html_chkbox), isRetry); + } + + task.execute(intent); + } + + /** + * Starts the task. + * + * @param intent The original intent. + * @param account The account. + * @param isRetry The retry flag. + */ + private void startEmailyTask(final Intent intent, final Account account, final boolean isRetry) + { + final GoogleAccountManager googleAccountManager = new GoogleAccountManager(Emaily.this); + + final long expiry = sharedPrefs.getLong(getString(R.string.prefs_key_googl_token_expiry), 0L); + final long now = System.currentTimeMillis(); + final long maxLife = (60L * 55L) * 1000L; // 55 minutes + + Log.d(appName, "Token Expires: " + new Date(expiry)); + + if (expiry >= (now + maxLife) || expiry <= now) + { + final String token = getPref(R.string.prefs_key_googl_token); + if (isValid(token)) + { + googleAccountManager.manager.invalidateAuthToken(ACCOUNT_TYPE, token); + + Log.d(appName, "Token Invalidated: " + token); + } + } + + googleAccountManager.manager.getAuthToken(account, OAUTH_URL, null, Emaily.this, new AccountManagerCallback() + { + @Override + public void run(AccountManagerFuture future) + { + try + { + final String token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN); + final Editor editor = sharedPrefs.edit(); + final long now = System.currentTimeMillis(); + + final long expires; + if (expiry < now) + { + expires = now + maxLife; + } + else + { + expires = expiry; + } + + editor.putLong(getString(R.string.prefs_key_googl_token_expiry), expires); + editor.putString(getString(R.string.prefs_key_googl_token), token); + editor.commit(); + + Log.d(appName, account.toString()); + Log.d(appName, "Token: " + token); + Log.d(appName, "Expires: " + new Date(expires)); + + startEmailyTask(intent, true, isRetry); + + } + catch (OperationCanceledException e) + { + Log.e(appName, "Auth token request has been canceled.", e); + } + catch (Exception e) + { + Log.e(appName, "Exception while requesting the auth token.", e); + } + } + }, null); + } + + /** + * Retries the task. + * + * @param intent The original intent. + */ + public void retry(final Intent intent) + { + sharedPrefs.edit().putLong(getString(R.string.prefs_key_googl_token_expiry), 0L).commit(); + + startEmailyTask(intent, new Account(getPref(R.string.prefs_key_googl_account), ACCOUNT_TYPE), true); + } + + /** + * Validates a string. + * + * @param s The string to validate. + * @return returns true if the string is not empty or null, false otherwise. + */ + public static boolean isValid(String s) + { + return (s != null) && (!s.trim().isEmpty()); + } + + /** + * Returns the value of the specified shared reference based on the specified string id. The default value is an empty string. + * + * @param id The string id. + * @return The preference value. + */ + public String getPref(int id) + { + return getPref(id, ""); + } + + /** + * Returns the value of the specified shared reference based on the specified string id. + * + * @param id The string id. + * @param defaultValue The default value, used if the preference is empty. + * @return The preference value. + */ + public String getPref(int id, String defaultValue) + { + return sharedPrefs.getString(getString(id), defaultValue); + } + + /** + * Returns the value of the specified shared reference based on the specified string id. The default value is false. + * + * @param id The string id. + * @return The preference value. + */ + public boolean getBoolPref(int id) + { + return getBoolPref(id, false); + } + + /** + * Returns the value of the specified shared reference based on the specified string id. + * + * @param id The string id. + * @param defaultValue The default value, used if the preference is empty. + * @return The preference value. + */ + public boolean getBoolPref(int id, boolean defaultValue) + { + return sharedPrefs.getBoolean(getString(id), defaultValue); + } + + /** + * The EmailyTask class. + */ + private class EmailyTask extends AsyncTask + { + private final ProgressDialog dialog = new ProgressDialog(Emaily.this); + private final String username; + private final String keytoken; + private final boolean isGoogl; + private final boolean isHtml; + private final boolean isRetry; + + public EmailyTask(String username, String keytoken, boolean isGoogl, boolean isHtml, boolean isRetry) + { + this.username = username; + this.keytoken = keytoken; + this.isGoogl = isGoogl; + this.isHtml = isHtml; + this.isRetry = isRetry; + } + + @Override + protected EmailyResult doInBackground(Intent... intent) + { + final EmailyResult result = new EmailyResult(intent[0]); + + final Intent emailIntent = new Intent(Intent.ACTION_SEND); + + if (isHtml) + { + emailIntent.setType("text/html"); + } + else + { + emailIntent.setType("text/plain"); + } + + + final Bundle extras = intent[0].getExtras(); + + final String pageUrl = extras.getString(Intent.EXTRA_TEXT); + final String pageTitle = extras.getString(Intent.EXTRA_SUBJECT); + final StringBuilder textBefore = new StringBuilder(); + + if (isValid(pageTitle)) + { + emailIntent.putExtra(Intent.EXTRA_SUBJECT, pageTitle); + } + + final boolean hasCredentials = isValid(username) && isValid(keytoken); + final StringBuilder shortUrl = new StringBuilder(); + + if (isValid(pageUrl)) + { + final HttpTransport transport = new NetHttpTransport(); + final JsonFactory jsonFactory = new JacksonFactory(); + + String version = ""; + + try + { + version = '/' + getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } + catch (NameNotFoundException ignore) + { + // Do nothing; + } + + final Url toInsert = new Url(); + + final String[] splits = pageUrl.split("\\s"); + + for (String item : splits) + { + try + { + new URL(item.trim()); + + if (isGoogl || !hasCredentials) + { + Log.d(appName, "goo.gl -> " + item); + + final Urlshortener shortener = Urlshortener.builder(transport, jsonFactory).setApplicationName(appName + version) + .setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() + { + @Override + public void initialize(JsonHttpRequest request) throws IOException + { + UrlshortenerRequest shortnerRequest = (UrlshortenerRequest) request; + + shortnerRequest.setKey(getString(R.string.secret_apikey)); + + if (isValid(keytoken)) + { + shortnerRequest.setOauthToken(keytoken); + + } + shortnerRequest.put("client_id", getString(R.string.secret_client_id)); + shortnerRequest.put("client_secret", getString(R.string.secret_client_secret)); + } + }).build(); + + toInsert.setLongUrl(item.trim()); + + try + { + final Url shortened = shortener.url().insert(toInsert).execute(); + + shortUrl.append(shortened.getId()); + } + catch (GoogleJsonResponseException e) + { + result.setCode(R.string.alert_error); + + final GoogleJsonError err = e.getDetails(); + + result.setMessage(err.message); + + if (err.code == 401) + { + if (!isRetry) + { + result.setRetry(true); + } + } + + Log.e(appName, "Exception while shortening '" + item + "' via goo.gl.", e); + } + catch (UnknownHostException e) + { + result.setCode(R.string.alert_nohost); + result.setMessage(e.getMessage()); + + Log.e(appName, "UnknownHostException while shortening '" + item + "' via goo.gl.", e); + } + catch (IOException e) + { + result.setCode(R.string.alert_error); + result.setMessage(e.getMessage()); + + Log.e(appName, "IOException while shortening '" + item + "' via goo.gl.", e); + } + } + else + { + Log.d(appName, "bit.ly -> " + item); + + try + { + shortUrl.append(as(username, keytoken).call(shorten(item.trim())).getShortUrl()); + } + catch (Exception e) + { + final Throwable cause = e.getCause(); + + if (cause != null && cause instanceof UnknownHostException) + { + result.setCode(R.string.alert_nohost); + result.setMessage(cause.getMessage()); + } + else + { + result.setCode(R.string.alert_error); + result.setMessage(e.getMessage()); + } + + Log.e(appName, "Exception while shortening '" + item + "' via bit.ly.", e); + } + + break; + } + + break; + } + catch (MalformedURLException mue) + { + Log.d(appName, "Attempted to process an invalid URL: " + item, mue); + + if (textBefore.length() > 0) + { + textBefore.append(" "); + } + + textBefore.append(item); + } + } + } + else + { + result.setCode(R.string.alert_nocreds); + } + + if (!result.isRetry()) + { + if (shortUrl.length() > 0) + { + if (isHtml) + { + emailIntent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml("" + shortUrl + "")); + } + else + { + emailIntent.putExtra(Intent.EXTRA_TEXT, shortUrl.toString()); + } + + if (!isValid(pageTitle) && textBefore.length() > 0) + { + emailIntent.putExtra(Intent.EXTRA_SUBJECT, textBefore.toString()); + } + } + else + { + final CharSequence chars = extras.getCharSequence(Intent.EXTRA_TEXT); + + if (chars.length() > 0) + { + emailIntent.putExtra(Intent.EXTRA_TEXT, chars); + } + else if (isValid(pageUrl)) + { + emailIntent.putExtra(Intent.EXTRA_TEXT, pageUrl); + } + } + + try + { + startActivity(emailIntent); + } + catch (android.content.ActivityNotFoundException ignore) + { + if (!result.hasError() && shortUrl.length() > 0) + { + final ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + clip.setText(shortUrl); + + result.setCode(R.string.alert_notfound_clip); + } + else + { + result.setCode(R.string.alert_notfound); + } + } + } + + return result; + } + + @Override + protected void onPostExecute(EmailyResult result) + { + if (this.dialog.isShowing()) + { + this.dialog.dismiss(); + } + + if (result.isRetry()) + { + Emaily.this.retry(result.getIntent()); + } + else + { + if (result.hasError()) + { + Toast.makeText( + getApplicationContext(), + getString(result.getCode(), result.getMessage(), isGoogl ? getString(R.string.prefs_googl_title) + : getString(R.string.prefs_bitly_title)), Toast.LENGTH_LONG).show(); + + } + + Emaily.this.finish(); + } + } + + @Override + protected void onPreExecute() + { + if (isRetry) + { + this.dialog.setMessage(getString(R.string.progress_msg_retry)); + } + else + { + this.dialog.setMessage(getString(R.string.progress_msg)); + } + + this.dialog.show(); + } + } + + /** + * The EmailyResult class. + */ + private class EmailyResult + { + private int code = 0; + private String message; + private boolean retry = false; + private final Intent intent; + + public EmailyResult(Intent intent) + { + this.intent = intent; + } + + public int getCode() + { + return code; + } + + public String getMessage() + { + if (isValid(message)) + { + return message; + } + else + { + return ""; + } + } + + public Intent getIntent() + { + return intent; + } + + public boolean hasError() + { + return code != 0; + } + + public boolean isRetry() + { + return retry; + } + + public void setCode(int code) + { + this.code = code; + } + + public void setMessage(String message) + { + this.message = message; + } + + public void setRetry(boolean retry) + { + this.retry = retry; + } + } +} \ No newline at end of file diff --git a/src/net/thauvin/erik/android/emaily/EmailyPrefs.java b/app/src/main/java/net/thauvin/erik/android/emaily/EmailyPrefs.java similarity index 98% rename from src/net/thauvin/erik/android/emaily/EmailyPrefs.java rename to app/src/main/java/net/thauvin/erik/android/emaily/EmailyPrefs.java index 98af351..ddbf6e9 100644 --- a/src/net/thauvin/erik/android/emaily/EmailyPrefs.java +++ b/app/src/main/java/net/thauvin/erik/android/emaily/EmailyPrefs.java @@ -88,7 +88,7 @@ public class EmailyPrefs extends PreferenceActivity implements OnSharedPreferenc mBitlyCreds.setEnabled(false); } - final Preference version = (Preference) findPreference(getString(R.string.prefs_key_version)); + final Preference version = findPreference(getString(R.string.prefs_key_version)); final PreferenceScreen feedback = (PreferenceScreen) findPreference(getString(R.string.prefs_key_feedback)); try { diff --git a/res/layout/bitlycreds.xml b/app/src/main/res/layout/bitlycreds.xml similarity index 100% rename from res/layout/bitlycreds.xml rename to app/src/main/res/layout/bitlycreds.xml diff --git a/res/drawable-hdpi/icon.png b/app/src/main/res/mipmap-hdpi/icon.png similarity index 100% rename from res/drawable-hdpi/icon.png rename to app/src/main/res/mipmap-hdpi/icon.png diff --git a/res/drawable-ldpi/icon.png b/app/src/main/res/mipmap-ldpi/icon.png similarity index 100% rename from res/drawable-ldpi/icon.png rename to app/src/main/res/mipmap-ldpi/icon.png diff --git a/res/drawable-mdpi/icon.png b/app/src/main/res/mipmap-mdpi/icon.png similarity index 100% rename from res/drawable-mdpi/icon.png rename to app/src/main/res/mipmap-mdpi/icon.png diff --git a/res/drawable-xhdpi/icon.png b/app/src/main/res/mipmap-xhdpi/icon.png similarity index 100% rename from res/drawable-xhdpi/icon.png rename to app/src/main/res/mipmap-xhdpi/icon.png diff --git a/res/values-v11/themes.xml b/app/src/main/res/values-v11/themes.xml similarity index 100% rename from res/values-v11/themes.xml rename to app/src/main/res/values-v11/themes.xml diff --git a/res/values-v21/themes.xml b/app/src/main/res/values-v21/themes.xml similarity index 100% rename from res/values-v21/themes.xml rename to app/src/main/res/values-v21/themes.xml diff --git a/res/values/.gitignore b/app/src/main/res/values/.gitignore similarity index 92% rename from res/values/.gitignore rename to app/src/main/res/values/.gitignore index fbe445d..ac869c6 100644 --- a/res/values/.gitignore +++ b/app/src/main/res/values/.gitignore @@ -1 +1 @@ -/secret.xml +/secret.xml diff --git a/res/values/strings.xml b/app/src/main/res/values/strings.xml similarity index 97% rename from res/values/strings.xml rename to app/src/main/res/values/strings.xml index fbaec01..13085b0 100644 --- a/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,7 +19,7 @@ http://bitly.com/a/your_api_key/ Username bit.ly - © 2012-14 Erik C. Thauvin + © 2012–14 Erik C. Thauvin %1$s %2$s %3$s (%4$s %5$s, %6$s) Send email, please check Help first… Feedback diff --git a/res/values/themes.xml b/app/src/main/res/values/themes.xml similarity index 100% rename from res/values/themes.xml rename to app/src/main/res/values/themes.xml diff --git a/res/xml/prefs.xml b/app/src/main/res/xml/prefs.xml similarity index 100% rename from res/xml/prefs.xml rename to app/src/main/res/xml/prefs.xml diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d3ff69d --- /dev/null +++ b/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.1.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1d3591c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..8c0fb64 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0c71e76 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/commons-codec-1.6-sources.jar b/lib/commons-codec-1.6-sources.jar deleted file mode 100644 index cc6abff..0000000 Binary files a/lib/commons-codec-1.6-sources.jar and /dev/null differ diff --git a/lib/commons-codec-1.6.jar b/lib/commons-codec-1.6.jar deleted file mode 100644 index ee1bc49..0000000 Binary files a/lib/commons-codec-1.6.jar and /dev/null differ diff --git a/lib/google-api-client-1.7.0-beta-sources.jar b/lib/google-api-client-1.7.0-beta-sources.jar deleted file mode 100644 index 911165c..0000000 Binary files a/lib/google-api-client-1.7.0-beta-sources.jar and /dev/null differ diff --git a/lib/google-api-client-android2-1.7.0-beta-sources.jar b/lib/google-api-client-android2-1.7.0-beta-sources.jar deleted file mode 100644 index 9665f69..0000000 Binary files a/lib/google-api-client-android2-1.7.0-beta-sources.jar and /dev/null differ diff --git a/lib/google-api-urlshortener-v1-rev2-java-1.4.0-beta-sources.jar b/lib/google-api-urlshortener-v1-rev2-java-1.4.0-beta-sources.jar deleted file mode 100644 index ae181ad..0000000 Binary files a/lib/google-api-urlshortener-v1-rev2-java-1.4.0-beta-sources.jar and /dev/null differ diff --git a/lib/google-http-client-1.7.0-beta-sources.jar b/lib/google-http-client-1.7.0-beta-sources.jar deleted file mode 100644 index def27a8..0000000 Binary files a/lib/google-http-client-1.7.0-beta-sources.jar and /dev/null differ diff --git a/lib/google-http-client-android2-1.7.0-beta-sources.jar b/lib/google-http-client-android2-1.7.0-beta-sources.jar deleted file mode 100644 index c287a85..0000000 Binary files a/lib/google-http-client-android2-1.7.0-beta-sources.jar and /dev/null differ diff --git a/lib/google-http-client-android3-1.7.0-beta-sources.jar b/lib/google-http-client-android3-1.7.0-beta-sources.jar deleted file mode 100644 index a37d895..0000000 Binary files a/lib/google-http-client-android3-1.7.0-beta-sources.jar and /dev/null differ diff --git a/lib/google-oauth-client-1.7.0-beta-sources.jar b/lib/google-oauth-client-1.7.0-beta-sources.jar deleted file mode 100644 index 2f62105..0000000 Binary files a/lib/google-oauth-client-1.7.0-beta-sources.jar and /dev/null differ diff --git a/lib/gson-2.1-sources.jar b/lib/gson-2.1-sources.jar deleted file mode 100644 index 09396a0..0000000 Binary files a/lib/gson-2.1-sources.jar and /dev/null differ diff --git a/lib/guava-11.0.1-sources.jar b/lib/guava-11.0.1-sources.jar deleted file mode 100644 index 778c0c4..0000000 Binary files a/lib/guava-11.0.1-sources.jar and /dev/null differ diff --git a/lib/jackson-core-asl-1.9.4-sources.jar b/lib/jackson-core-asl-1.9.4-sources.jar deleted file mode 100644 index a9c9aae..0000000 Binary files a/lib/jackson-core-asl-1.9.4-sources.jar and /dev/null differ diff --git a/lib/protobuf-java-2.2.0-sources.jar b/lib/protobuf-java-2.2.0-sources.jar deleted file mode 100644 index fe5e028..0000000 Binary files a/lib/protobuf-java-2.2.0-sources.jar and /dev/null differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/src/net/thauvin/erik/android/emaily/Emaily.java b/src/net/thauvin/erik/android/emaily/Emaily.java deleted file mode 100644 index a360a47..0000000 --- a/src/net/thauvin/erik/android/emaily/Emaily.java +++ /dev/null @@ -1,705 +0,0 @@ -/* - * @(#)Emaily.java - * - * Copyright (c) 2011-2012 Erik C. Thauvin (http://erik.thauvin.net/) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * Neither the name of the authors nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * $Id$ - * - */ -package net.thauvin.erik.android.emaily; - -import static com.rosaloves.bitlyj.Bitly.as; -import static com.rosaloves.bitlyj.Bitly.shorten; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.Date; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerCallback; -import android.accounts.AccountManagerFuture; -import android.accounts.OperationCanceledException; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.content.pm.PackageManager.NameNotFoundException; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.ClipboardManager; -import android.text.Html; -import android.util.Log; -import android.widget.Toast; - -import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager; -import com.google.api.client.googleapis.json.GoogleJsonError; -import com.google.api.client.googleapis.json.GoogleJsonResponseException; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.http.json.JsonHttpRequest; -import com.google.api.client.http.json.JsonHttpRequestInitializer; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.jackson.JacksonFactory; -import com.google.api.services.urlshortener.Urlshortener; -import com.google.api.services.urlshortener.UrlshortenerRequest; -import com.google.api.services.urlshortener.model.Url; - -/** - * The Emaily class implements a URL shortener intent. - * - * @author Erik C. Thauvin - * @version $Revision$ - * @created Oct 11, 2011 - * @since 1.0 - */ -@SuppressWarnings("deprecation") -public class Emaily extends Activity -{ - private static final String ACCOUNT_TYPE = "com.google"; - private static final String OAUTH_URL = "oauth2:https://www.googleapis.com/auth/urlshortener"; - - private String appName; - private SharedPreferences sharedPrefs; - - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - final Intent intent = getIntent(); - - appName = getString(R.string.app_name); - sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - - if (Intent.ACTION_SEND.equals(intent.getAction())) - { - final boolean isGoogl = getBoolPref(R.string.prefs_key_googl_enabled, true); - - if (isGoogl) - { - final String account = getPref(R.string.prefs_key_googl_account); - - if (isValid(account)) - { - startEmailyTask(intent, new Account(account, ACCOUNT_TYPE), false); - } - else - { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.dialog_accounts_title); - - final Account[] accounts = AccountManager.get(this).getAccountsByType(ACCOUNT_TYPE); - final int size = accounts.length; - if (size > 0) - { - if (size == 1) - { - startEmailyTask(intent, accounts[0], false); - } - else - { - final CharSequence[] names = new CharSequence[size]; - for (int i = 0; i < size; i++) - { - names[i] = accounts[i].name; - } - - builder.setSingleChoiceItems(names, 0, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) - { - dialog.dismiss(); - - final Editor editor = sharedPrefs.edit(); - editor.putString(getString(R.string.prefs_key_googl_account), names[which].toString()); - editor.putLong(getString(R.string.prefs_key_googl_token_expiry), 0L); - editor.commit(); - - startEmailyTask(intent, accounts[which], false); - } - - }); - - builder.create().show(); - } - } - else - { - startEmailyTask(intent, isGoogl, false); - } - } - } - else - { - startEmailyTask(intent, isGoogl, false); - } - } - else - { - Emaily.this.finish(); - } - - } - - /** - * Starts the task. - * - * @param intent The original intent. - * @param sharedPrefs The shared preference. - * @param isGoogl The goo.gl flag. - * @param isRetry The retry flag. - */ - private void startEmailyTask(final Intent intent, final boolean isGoogl, final boolean isRetry) - { - final EmailyTask task; - - if (isGoogl) - { - task = new EmailyTask(getPref(R.string.prefs_key_googl_account), getPref(R.string.prefs_key_googl_token), isGoogl, - getBoolPref(R.string.prefs_key_html_chkbox), isRetry); - } - else - { - task = new EmailyTask(getPref(R.string.prefs_key_bitly_username), getPref(R.string.prefs_key_bitly_apikey), isGoogl, - getBoolPref(R.string.prefs_key_html_chkbox), isRetry); - } - - task.execute(intent); - } - - /** - * Starts the task. - * - * @param intent The original intent. - * @param account The account. - * @param isRetry The retry flag. - */ - private void startEmailyTask(final Intent intent, final Account account, final boolean isRetry) - { - final GoogleAccountManager googleAccountManager = new GoogleAccountManager(Emaily.this); - - final long expiry = sharedPrefs.getLong(getString(R.string.prefs_key_googl_token_expiry), 0L); - final long now = System.currentTimeMillis(); - final long maxLife = (60L * 55L) * 1000L; // 55 minutes - - Log.d(appName, "Token Expires: " + new Date(expiry)); - - if (expiry >= (now + maxLife) || expiry <= now) - { - final String token = getPref(R.string.prefs_key_googl_token); - if (isValid(token)) - { - googleAccountManager.manager.invalidateAuthToken(ACCOUNT_TYPE, token); - - Log.d(appName, "Token Invalidated: " + token); - } - } - - googleAccountManager.manager.getAuthToken(account, OAUTH_URL, null, Emaily.this, new AccountManagerCallback() - { - @Override - public void run(AccountManagerFuture future) - { - try - { - final String token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN); - final Editor editor = sharedPrefs.edit(); - final long now = System.currentTimeMillis(); - - final long expires; - if (expiry < now) - { - expires = now + maxLife; - } - else - { - expires = expiry; - } - - editor.putLong(getString(R.string.prefs_key_googl_token_expiry), expires); - editor.putString(getString(R.string.prefs_key_googl_token), token); - editor.commit(); - - Log.d(appName, account.toString()); - Log.d(appName, "Token: " + token); - Log.d(appName, "Expires: " + new Date(expires)); - - startEmailyTask(intent, true, isRetry); - - } - catch (OperationCanceledException e) - { - Log.e(appName, "Auth token request has been canceled.", e); - } - catch (Exception e) - { - Log.e(appName, "Exception while requesting the auth token.", e); - } - } - }, null); - } - - /** - * Retries the task. - * - * @param intent The original intent. - */ - public void retry(final Intent intent) - { - sharedPrefs.edit().putLong(getString(R.string.prefs_key_googl_token_expiry), 0L).commit(); - - startEmailyTask(intent, new Account(getPref(R.string.prefs_key_googl_account), ACCOUNT_TYPE), true); - } - - /** - * Validates a string. - * - * @param s The string to validate. - * @return returns true if the string is not empty or null, false otherwise. - */ - public static boolean isValid(String s) - { - return (s != null) && (!s.trim().isEmpty()); - } - - /** - * Returns the value of the specified shared reference based on the specified string id. The default value is an empty string. - * - * @param sharedPrefs The shared preference. - * @param id The string id. - * @return The preference value. - */ - public String getPref(int id) - { - return getPref(id, ""); - } - - /** - * Returns the value of the specified shared reference based on the specified string id. - * - * @param sharedPrefs The shared preference. - * @param id The string id. - * @param defaultValue The default value, used if the preference is empty. - * @return The preference value. - */ - public String getPref(int id, String defaultValue) - { - return sharedPrefs.getString(getString(id), defaultValue); - } - - /** - * Returns the value of the specified shared reference based on the specified string id. The default value is false. - * - * @param sharedPrefs The shared preference. - * @param id The string id. - * @return The preference value. - */ - public boolean getBoolPref(int id) - { - return getBoolPref(id, false); - } - - /** - * Returns the value of the specified shared reference based on the specified string id. - * - * @param sharedPrefs The shared preference. - * @param id The string id. - * @param defaultValue The default value, used if the preference is empty. - * @return The preference value. - */ - public boolean getBoolPref(int id, boolean defaultValue) - { - return sharedPrefs.getBoolean(getString(id), defaultValue); - } - - /** - * The EmailyTask class. - */ - private class EmailyTask extends AsyncTask - { - private final ProgressDialog dialog = new ProgressDialog(Emaily.this); - private final String username; - private final String keytoken; - private final boolean isGoogl; - private final boolean isHtml; - private final boolean isRetry; - - public EmailyTask(String username, String keytoken, boolean isGoogl, boolean isHtml, boolean isRetry) - { - this.username = username; - this.keytoken = keytoken; - this.isGoogl = isGoogl; - this.isHtml = isHtml; - this.isRetry = isRetry; - } - - @Override - protected EmailyResult doInBackground(Intent... intent) - { - final EmailyResult result = new EmailyResult(intent[0]); - - final Intent emailIntent = new Intent(Intent.ACTION_SEND); - - if (isHtml) - { - emailIntent.setType("text/html"); - } - else - { - emailIntent.setType("text/plain"); - } - - - final Bundle extras = intent[0].getExtras(); - - final String pageUrl = extras.getString(Intent.EXTRA_TEXT); - final String pageTitle = extras.getString(Intent.EXTRA_SUBJECT); - final StringBuilder textBefore = new StringBuilder(); - - if (isValid(pageTitle)) - { - emailIntent.putExtra(Intent.EXTRA_SUBJECT, pageTitle); - } - - final boolean hasCredentials = isValid(username) && isValid(keytoken); - final StringBuilder shortUrl = new StringBuilder(); - - if (isValid(pageUrl)) - { - final HttpTransport transport = new NetHttpTransport(); - final JsonFactory jsonFactory = new JacksonFactory(); - - String version = ""; - - try - { - version = '/' + getPackageManager().getPackageInfo(getPackageName(), 0).versionName; - } - catch (NameNotFoundException ignore) - { - // Do nothing; - } - - final Url toInsert = new Url(); - - final String[] splits = pageUrl.split("\\s"); - - for (String item : splits) - { - try - { - new URL(item.trim()); - - if (isGoogl || !hasCredentials) - { - Log.d(appName, "goo.gl -> " + item); - - final Urlshortener shortener = com.google.api.services.urlshortener.Urlshortener - .builder(transport, jsonFactory).setApplicationName(appName + version) - .setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() - { - @Override - public void initialize(JsonHttpRequest request) throws IOException - { - UrlshortenerRequest shortnerRequest = (UrlshortenerRequest) request; - - shortnerRequest.setKey(getString(R.string.secret_apikey)); - - if (isValid(keytoken)) - { - shortnerRequest.setOauthToken(keytoken); - - } - shortnerRequest.put("client_id", getString(R.string.secret_client_id)); - shortnerRequest.put("client_secret", getString(R.string.secret_client_secret)); - } - }).build(); - - toInsert.setLongUrl(item.trim()); - - try - { - final Url shortened = shortener.url().insert(toInsert).execute(); - - shortUrl.append(shortened.getId()); - } - catch (GoogleJsonResponseException e) - { - result.setCode(R.string.alert_error); - - final GoogleJsonError err = e.getDetails(); - - result.setMessage(err.message); - - if (err.code == 401) - { - if (!isRetry) - { - result.setRetry(true); - } - } - - Log.e(appName, "Exception while shortening '" + item + "' via goo.gl.", e); - } - catch (UnknownHostException e) - { - result.setCode(R.string.alert_nohost); - result.setMessage(e.getMessage()); - - Log.e(appName, "UnknownHostException while shortening '" + item + "' via goo.gl.", e); - } - catch (IOException e) - { - result.setCode(R.string.alert_error); - result.setMessage(e.getMessage()); - - Log.e(appName, "IOException while shortening '" + item + "' via goo.gl.", e); - } - } - else - { - Log.d(appName, "bit.ly -> " + item); - - try - { - shortUrl.append(as(username, keytoken).call(shorten(item.trim())).getShortUrl()); - } - catch (Exception e) - { - final Throwable cause = e.getCause(); - - if (cause != null && cause instanceof UnknownHostException) - { - result.setCode(R.string.alert_nohost); - result.setMessage(cause.getMessage()); - } - else - { - result.setCode(R.string.alert_error); - result.setMessage(e.getMessage()); - } - - Log.e(appName, "Exception while shortening '" + item + "' via bit.ly.", e); - } - - break; - } - - break; - } - catch (MalformedURLException mue) - { - Log.d(appName, "Attempted to process an invalid URL: " + item, mue); - - if (textBefore.length() > 0) - { - textBefore.append(" "); - } - - textBefore.append(item); - } - } - } - else - { - result.setCode(R.string.alert_nocreds); - } - - if (!result.isRetry()) - { - if (shortUrl.length() > 0) - { - if (isHtml) - { - emailIntent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml("" + shortUrl + "")); - } - else - { - emailIntent.putExtra(Intent.EXTRA_TEXT, shortUrl.toString()); - } - - if (!isValid(pageTitle) && textBefore.length() > 0) - { - emailIntent.putExtra(Intent.EXTRA_SUBJECT, textBefore.toString()); - } - } - else - { - final CharSequence chars = extras.getCharSequence(Intent.EXTRA_TEXT); - - if (chars.length() > 0) - { - emailIntent.putExtra(Intent.EXTRA_TEXT, chars); - } - else if (isValid(pageUrl)) - { - emailIntent.putExtra(Intent.EXTRA_TEXT, pageUrl); - } - } - - try - { - startActivity(emailIntent); - } - catch (android.content.ActivityNotFoundException ignore) - { - if (!result.hasError() && shortUrl.length() > 0) - { - final ClipboardManager clip = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); - clip.setText(shortUrl); - - result.setCode(R.string.alert_notfound_clip); - } - else - { - result.setCode(R.string.alert_notfound); - } - } - } - - return result; - } - - @Override - protected void onPostExecute(EmailyResult result) - { - if (this.dialog.isShowing()) - { - this.dialog.dismiss(); - } - - if (result.isRetry()) - { - Emaily.this.retry(result.getIntent()); - } - else - { - if (result.hasError()) - { - Toast.makeText( - getApplicationContext(), - getString(result.getCode(), result.getMessage(), isGoogl ? getString(R.string.prefs_googl_title) - : getString(R.string.prefs_bitly_title)), Toast.LENGTH_LONG).show(); - - } - - Emaily.this.finish(); - } - } - - @Override - protected void onPreExecute() - { - if (isRetry) - { - this.dialog.setMessage(getString(R.string.progress_msg_retry)); - } - else - { - this.dialog.setMessage(getString(R.string.progress_msg)); - } - - this.dialog.show(); - } - } - - /** - * The EmailyResult class. - */ - private class EmailyResult - { - private int code = 0; - private String message; - private boolean retry = false; - private final Intent intent; - - public EmailyResult(Intent intent) - { - this.intent = intent; - } - - public int getCode() - { - return code; - } - - public String getMessage() - { - if (isValid(message)) - { - return message; - } - else - { - return ""; - } - } - - public Intent getIntent() - { - return intent; - } - - public boolean hasError() - { - return code != 0; - } - - public boolean isRetry() - { - return retry; - } - - public void setCode(int code) - { - this.code = code; - } - - public void setMessage(String message) - { - this.message = message; - } - - public void setRetry(boolean retry) - { - this.retry = retry; - } - } -} \ No newline at end of file