Version 1.0 Release

This commit is contained in:
Erik C. Thauvin 2012-04-03 22:12:05 +00:00
parent 2b84e4d824
commit ae7f0c9838
52 changed files with 1201 additions and 0 deletions

42
AndroidManifest.xml Normal file
View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.thauvin.erik.android.emaily"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="10"
android:targetSdkVersion="11" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/Emaily" >
<activity
android:name="net.thauvin.erik.android.emaily.Emaily"
android:label="@string/app_name"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name=".EmailyPrefs"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

BIN
lib/bitlyj-2.0.0.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/commons-codec-1.6.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/gson-2.1-sources.jar Normal file

Binary file not shown.

BIN
lib/gson-2.1.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/guava-11.0.1.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/jsr305-1.3.9.jar Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/protobuf-java-2.2.0.jar Normal file

Binary file not shown.

64
proguard.cfg Normal file
View file

@ -0,0 +1,64 @@
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# Needed by google-http-client to keep generic types and @Key annotations accessed via reflection
-keepclassmembers class * {
@com.google.api.client.util.Key <fields>;
}
# Needed just to be safe in terms of keeping Google API service model classes
-keep class com.google.api.services.*.model.*
-keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
# Needed by Guava
-dontwarn sun.misc.Unsafe
# See https://groups.google.com/forum/#!topic/guava-discuss/YCZzeCiIVoI
-dontwarn com.google.common.collect.MinMaxPriorityQueue
# Emaily
-keep class com.google.api.client.googleapis.json.*
-dontwarn org.apache.commons.codec.binary.StringUtils
-dontwarn org.apache.commons.codec.binary.Base64

BIN
res/drawable-hdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
res/drawable-ldpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/drawable-mdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
res/drawable-xhdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

64
res/layout/bitlycreds.xml Normal file
View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="@+id/bit_username_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:gravity="left"
android:text="@string/prefs_bitly_creds_username"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/bitly_username_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:gravity="fill_horizontal"
android:inputType="text"
android:key="@string/prefs_key_bitly_username"
android:scrollHorizontally="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/bitly_apikey_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:gravity="left"
android:text="@string/prefs_bitly_creds_apikey"
android:textAppearance="?android:attr/textAppearanceMedium" />
<EditText
android:id="@+id/bitly_apikey_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
android:gravity="fill_horizontal"
android:inputType="text"
android:key="@string/prefs_key_bitly_apikey"
android:scrollHorizontally="true"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/bitly_text_fld"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="20dip"
android:layout_marginTop="20dip"
android:gravity="center"
android:text="@string/prefs_bitly_creds_noapi"
android:textColor="#eda712"
android:clickable="true"
android:textAppearance="?android:attr/textAppearanceMedium" >
</TextView>
</LinearLayout>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Emaily" parent="@android:style/Theme.Holo" />
</resources>

46
res/values/strings.xml Normal file
View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="alert_error">Sorry. An error was returned by %2$s while shortening the url: %1$s</string>
<string name="alert_nocreds">Please provide your credentials to shorten urls.</string>
<string name="alert_nohost">Sorry. Could not connect to %1$s.</string>
<string name="alert_notfound">Sorry. No applications can perform this action.</string>
<string name="alert_notfound_clip">Sorry. No applications can perform this action. The shortened url has been copied to the clipboard.</string>
<string name="app_name">Emaily</string>
<string name="dialog_accounts_title">Select a Google account</string>
<string name="prefs_about_title">About</string>
<string name="prefs_bitly_creds_apikey">API Key</string>
<string name="prefs_bitly_creds_cancel">Cancel</string>
<string name="prefs_bitly_creds_dialog_title">Bit.ly API Credentials</string>
<string name="prefs_bitly_creds_noapi"><u>Need an API key?</u></string>
<string name="prefs_bitly_creds_ok">OK</string>
<string name="prefs_bitly_creds_summary">Enter your credentials…</string>
<string name="prefs_bitly_creds_title">API Credentials</string>
<string name="prefs_bitly_creds_url">http://bitly.com/a/your_api_key/</string>
<string name="prefs_bitly_creds_username">Username</string>
<string name="prefs_bitly_title">bit.ly</string>
<string name="prefs_copyright">© 2012 Erik C. Thauvin</string>
<string name="prefs_feedback_subject">%1$s %2$s %3$s (%4$s %5$s, %6$s)</string>
<string name="prefs_feedback_summary">Send email, please check Help first…</string>
<string name="prefs_feedback_title">Feedback</string>
<string name="prefs_feedback_url">mailto:erik@thauvin.net</string>
<string name="prefs_googl_chkbox_title">Use as shorterner?</string>
<string name="prefs_googl_title">goo.gl</string>
<string name="prefs_help_summary">Learn how to use…</string>
<string name="prefs_help_title">Help</string>
<string name="prefs_help_url">http://m.thauvin.net/android/Emaily/help/</string>
<string name="prefs_key_bitly_apikey">prefs_bitly_apikey</string>
<string name="prefs_key_bitly_creds">prefs_bitly_creds</string>
<string name="prefs_key_bitly_username">prefs_bitly_username</string>
<string name="prefs_key_feedback">prefs_feedback</string>
<string name="prefs_key_googl_account">prefs_googl_account</string>
<string name="prefs_key_googl_chkbox">prefs_googl_chkbox</string>
<string name="prefs_key_googl_enabled">prefs_google_enabled</string>
<string name="prefs_key_googl_token">prefs_googl_token</string>
<string name="prefs_key_googl_token_expiry">prefs_gool_token_expiry</string>
<string name="prefs_key_version">prefs_version</string>
<string name="prefs_version_title">Version</string>
<string name="progress_msg">Shortening url…</string>
<string name="progress_msg_retry">Retrying…</string>
</resources>

6
res/values/themes.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Emaily" parent="@android:style/Theme" />
</resources>

46
res/xml/prefs.xml Normal file
View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory android:title="@string/prefs_googl_title" >
<CheckBoxPreference
android:defaultValue="true"
android:key="@string/prefs_key_googl_chkbox"
android:title="@string/prefs_googl_chkbox_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/prefs_bitly_title" >
<net.thauvin.erik.android.emaily.BitlyCredsDialog
android:dialogLayout="@layout/bitlycreds"
android:dialogTitle="@string/prefs_bitly_creds_dialog_title"
android:key="@string/prefs_key_bitly_creds"
android:negativeButtonText="@string/prefs_bitly_creds_cancel"
android:positiveButtonText="@string/prefs_bitly_creds_ok"
android:summary="@string/prefs_bitly_creds_summary"
android:title="@string/prefs_bitly_creds_title" >
</net.thauvin.erik.android.emaily.BitlyCredsDialog>
</PreferenceCategory>
<PreferenceCategory android:title="@string/prefs_about_title" >
<Preference
android:enabled="false"
android:key="@string/prefs_key_version"
android:singleLine="true"
android:summary="@string/prefs_copyright"
android:title="@string/prefs_version_title" />
<PreferenceScreen
android:summary="@string/prefs_help_summary"
android:title="@string/prefs_help_title" >
<intent
android:action="android.intent.action.VIEW"
android:data="@string/prefs_help_url" />
</PreferenceScreen>
<PreferenceScreen
android:key="@string/prefs_key_feedback"
android:summary="@string/prefs_feedback_summary"
android:title="@string/prefs_feedback_title" >
<intent
android:action="android.intent.action.VIEW"
android:data="@string/prefs_feedback_url" />
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>

BIN
screenshots/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
screenshots/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
screenshots/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
screenshots/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
screenshots/emaily.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
screenshots/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
screenshots/icon114x114.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -0,0 +1,109 @@
/*
* @(#)Emaily.java
*
* Copyright (c) 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 android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
/**
* The <code>BitlyCredsDialog</code> class implements a bit.ly credential dialog.
*
* @author <a href="mailto:erik@thauvin.net">Erik C. Thauvin</a>
* @version $Revision$
* @created March 28, 2012
* @since 1.0
*/
public class BitlyCredsDialog extends DialogPreference
{
private final Context mContext;
private EditText username;
private EditText apikey;
public BitlyCredsDialog(Context context, AttributeSet attrs)
{
super(context, attrs);
mContext = context;
setPersistent(false);
}
@Override
protected void onBindDialogView(View view)
{
super.onBindDialogView(view);
final SharedPreferences sharedPrefs = getSharedPreferences();
username = (EditText) view.findViewById(R.id.bitly_username_edit);
apikey = (EditText) view.findViewById(R.id.bitly_apikey_edit);
final TextView textFld = (TextView) view.findViewById(R.id.bitly_text_fld);
username.setText(sharedPrefs.getString(mContext.getString(R.string.prefs_key_bitly_username), ""));
apikey.setText(sharedPrefs.getString(mContext.getString(R.string.prefs_key_bitly_apikey), ""));
textFld.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mContext.getString(R.string.prefs_bitly_creds_url)));
mContext.startActivity(intent);
};
});
}
@Override
protected void onDialogClosed(boolean positiveResult)
{
super.onDialogClosed(positiveResult);
if (positiveResult)
{
final SharedPreferences sharedPrefs = getSharedPreferences();
final Editor editor = sharedPrefs.edit();
editor.putString(mContext.getString(R.string.prefs_key_bitly_username), username.getText().toString());
editor.putString(mContext.getString(R.string.prefs_key_bitly_apikey), apikey.getText().toString());
editor.commit();
}
}
}

View file

@ -0,0 +1,648 @@
/*
* @(#)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 <code>Emaily</code> class implements a URL shortener intent.
*
* @author <a href="mailto:erik@thauvin.net">Erik C. Thauvin</a>
* @version $Revision$
* @created Oct 11, 2011
* @since 1.0
*/
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 = getApplicationContext().getResources().getString(R.string.app_name);
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (Intent.ACTION_SEND.equals(intent.getAction()))
{
final boolean isGoogl = sharedPrefs.getBoolean(getString(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, isRetry);
}
else
{
task = new EmailyTask(getPref(R.string.prefs_key_bitly_username), getPref(R.string.prefs_key_bitly_apikey), isGoogl, isRetry);
}
task.execute(intent);
}
/**
* Starts the task.
*
* @param intent The original intent.
* @param sharedPrefs The shared preference.
* @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<Bundle>()
{
@Override
public void run(AccountManagerFuture<Bundle> 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 <code>true</code> if the string is not empty or null, <code>false</code> 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 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);
}
/**
* The <code>EmailyTask</code> class.
*/
private class EmailyTask extends AsyncTask<Intent, Void, EmailyResult>
{
private final ProgressDialog dialog = new ProgressDialog(Emaily.this);
private final String username;
private final String keytoken;
private final boolean isGoogl;
private final boolean isRetry;
public EmailyTask(String username, String keytoken, boolean isGoogl, boolean isRetry)
{
this.username = username;
this.keytoken = keytoken;
this.isGoogl = isGoogl;
this.isRetry = isRetry;
}
@Override
protected EmailyResult doInBackground(Intent... intent)
{
final EmailyResult result = new EmailyResult(intent[0]);
final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
emailIntent.setType("text/html");
final Bundle extras = intent[0].getExtras();
final String pageUrl = extras.getString("android.intent.extra.TEXT");
final String pageTitle = extras.getString("android.intent.extra.SUBJECT");
if (isValid(pageTitle))
{
emailIntent.putExtra(android.content.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();
for (String item : pageUrl.split("\\s"))
{
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);
}
}
}
else
{
result.setCode(R.string.alert_nocreds);
}
if (!result.isRetry())
{
if (shortUrl.length() > 0)
{
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT,
Html.fromHtml("<a href=\"" + shortUrl + "\">" + shortUrl + "</a>"));
}
else
{
final CharSequence text = extras.getCharSequence("android.intent.extra.TEXT");
if (text.length() > 0)
{
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, text);
}
else if (isValid(pageUrl))
{
emailIntent.putExtra(android.content.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 <code>EmailyResult</code> 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;
}
}
}

View file

@ -0,0 +1,170 @@
/*
* @(#)EmailyPrefs.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 android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
/**
* The <code>EmailyPrefs</code> class implements a preferences screen.
*
* @author <a href="mailto:erik@thauvin.net">Erik C. Thauvin</a>
* @version $Revision$
* @created Oct 11, 2011
* @since 1.0
*/
public class EmailyPrefs extends PreferenceActivity implements OnSharedPreferenceChangeListener
{
private SharedPreferences sharedPrefs;
private Editor prefsEditor;
private CheckBoxPreference mGooglBox;
private BitlyCredsDialog mBitlyCreds;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs);
sharedPrefs = getPreferenceScreen().getSharedPreferences();
prefsEditor = sharedPrefs.edit();
mGooglBox = (CheckBoxPreference) findPreference(getString(R.string.prefs_key_googl_chkbox));
mBitlyCreds = (BitlyCredsDialog) findPreference(getString(R.string.prefs_key_bitly_creds));
setSummary(mBitlyCreds, getString(R.string.prefs_key_bitly_username), getString(R.string.prefs_bitly_creds_summary));
setSummary(mGooglBox, getString(R.string.prefs_key_googl_account), "");
if (mGooglBox.isChecked())
{
mBitlyCreds.setEnabled(false);
}
final Preference version = (Preference) findPreference(getString(R.string.prefs_key_version));
final PreferenceScreen feedback = (PreferenceScreen) findPreference(getString(R.string.prefs_key_feedback));
try
{
final String vNumber = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
version.setTitle(getString(R.string.prefs_version_title) + ' ' + vNumber);
feedback.getIntent().setData(
Uri.parse(getString(R.string.prefs_feedback_url)
+ "?subject="
+ getString(R.string.prefs_feedback_subject, getString(R.string.app_name), vNumber,
getString(R.string.prefs_feedback_title).toLowerCase(), Build.MANUFACTURER, Build.PRODUCT,
Build.VERSION.RELEASE)));
}
catch (NameNotFoundException ignore)
{
// Do nothing.
}
}
@Override
protected void onResume()
{
super.onResume();
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause()
{
super.onPause();
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)
{
if (key.equals(getString(R.string.prefs_key_bitly_username)))
{
setSummary(mBitlyCreds, key, getString(R.string.prefs_bitly_creds_summary));
}
else if (key.equals(getString(R.string.prefs_key_googl_chkbox)))
{
final boolean checked = mGooglBox.isChecked();
mBitlyCreds.setEnabled(!checked);
prefsEditor.putBoolean(getString(R.string.prefs_key_googl_enabled), checked);
if (!checked)
{
prefsEditor.putString(getString(R.string.prefs_key_googl_account), "");
prefsEditor.putLong(getString(R.string.prefs_key_googl_token_expiry), 0L);
mGooglBox.setSummary("");
}
prefsEditor.commit();
}
}
/**
* Sets a preference's summary.
*
* @param editPref The preference.
* @param key The preference key.
* @param defValue The default value.
*/
private void setSummary(Preference editPref, String key, String defValue)
{
final String value = sharedPrefs.getString(key, defValue);
if (Emaily.isValid(value))
{
editPref.setSummary(value);
}
else
{
editPref.setSummary(defValue);
}
}
}