diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f1efd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +bin +gen +*.apk +.hg/ +local.properties +*.DS_Store +*.BridgeSort +out/ +tmp/ +build/ +.gradle/ +.idea/ +*.iml +.project +.classpath +*.pem +mirror/ diff --git a/AppRate/.classpath b/AppRate/.classpath index a662f00..d57ec02 100644 --- a/AppRate/.classpath +++ b/AppRate/.classpath @@ -1,8 +1,9 @@ - - - - - - - - + + + + + + + + + diff --git a/AppRate/build.gradle b/AppRate/build.gradle new file mode 100644 index 0000000..3832df7 --- /dev/null +++ b/AppRate/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'android-library' + +dependencies { + compile fileTree(dir: 'libs', include: '*.jar') +} + +android { + compileSdkVersion 17 + buildToolsVersion "18.1.0" + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + + // Move the tests to tests/java, tests/res, etc... + instrumentTest.setRoot('tests') + + // Move the build types to build-types/ + // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... + // This moves them out of them default location under src//... which would + // conflict with src/ being used by the main source set. + // Adding new build types or product flavors should be accompanied + // by a similar customization. + debug.setRoot('build-types/debug') + release.setRoot('build-types/release') + } +} diff --git a/AppRate/res/values/strings.xml b/AppRate/res/values/strings.xml index 8add2c1..226908c 100644 --- a/AppRate/res/values/strings.xml +++ b/AppRate/res/values/strings.xml @@ -1,5 +1,26 @@ AppRate + Rate %1$s + "If you enjoy using %1$s, please take a moment to rate it. Thanks for your support!" + Rate it! + Remind me later + No thanks + Tell Us + "Do you like %1$s?" + Yes + No + Oh no! + Please let us know what we can do to make it better for you. + Send Feedback + Cancel + No Play Store installed on device + (unknown) + "%1$s Feedback" + Please enter your feedback above this line + Send Email… + Android Device: %s + Android Version: %s + App Version: %s \ No newline at end of file diff --git a/AppRate/src/com/tjeannin/apprate/AppInfo.java b/AppRate/src/com/tjeannin/apprate/AppInfo.java new file mode 100644 index 0000000..357e1d3 --- /dev/null +++ b/AppRate/src/com/tjeannin/apprate/AppInfo.java @@ -0,0 +1,75 @@ +package com.tjeannin.apprate; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +/** + * Created by bdonahue on 1/10/14. + */ +public class AppInfo { + private static final String TAG = AppInfo.class.getSimpleName(); + + /** + * @param context A context of the current application. + * @return The application name of the current application. + */ + public static final String getApplicationName(Context context) { + final PackageManager packageManager = context.getPackageManager(); + ApplicationInfo applicationInfo; + + try { + applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0); + } catch (final PackageManager.NameNotFoundException e) { + applicationInfo = null; + } + + return (String) (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : context.getString(R.string.application_name_unknown)); + } + + /** + * Get the application version code + * + * @param context + * @return The version name or null if there was an error + */ + public static final Integer getApplicationVersionCode(Context context) { + try { + if (context != null) { + PackageManager pm = context.getPackageManager(); + if (pm != null) { + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + if (pi != null) { + return pi.versionCode; + } + } + } + + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to get application version code"); + } + + return 0; + } + + public static String getApplicationVersionName(Context context) { + try { + if (context != null) { + PackageManager pm = context.getPackageManager(); + if (pm != null) { + PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); + if (pi != null) { + return pi.versionName; + } + } + } + + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to get application version name"); + } + + return ""; + } +} diff --git a/AppRate/src/com/tjeannin/apprate/AppRate.java b/AppRate/src/com/tjeannin/apprate/AppRate.java index 1ee8b3a..4684a45 100644 --- a/AppRate/src/com/tjeannin/apprate/AppRate.java +++ b/AppRate/src/com/tjeannin/apprate/AppRate.java @@ -1,7 +1,5 @@ package com.tjeannin.apprate; -import java.lang.Thread.UncaughtExceptionHandler; - import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; @@ -12,263 +10,615 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.text.format.DateUtils; import android.util.Log; +import android.widget.Button; import android.widget.Toast; -public class AppRate implements android.content.DialogInterface.OnClickListener, OnCancelListener { - - private static final String TAG = "AppRater"; - - private Activity hostActivity; - private OnClickListener clickListener; - private SharedPreferences preferences; - private AlertDialog.Builder dialogBuilder = null; - - private long minLaunchesUntilPrompt = 0; - private long minDaysUntilPrompt = 0; - - private boolean showIfHasCrashed = true; - - - public AppRate(Activity hostActivity) { - this.hostActivity = hostActivity; - preferences = hostActivity.getSharedPreferences(PrefsContract.SHARED_PREFS_NAME, 0); - } - - /** - * @param minLaunchesUntilPrompt The minimum number of times the user lunches the application before showing the rate dialog.
- * Default value is 0 times. - * @return This {@link AppRate} object to allow chaining. - */ - public AppRate setMinLaunchesUntilPrompt(long minLaunchesUntilPrompt) { - this.minLaunchesUntilPrompt = minLaunchesUntilPrompt; - return this; - } - - /** - * @param minDaysUntilPrompt The minimum number of days before showing the rate dialog.
- * Default value is 0 days. - * @return This {@link AppRate} object to allow chaining. - */ - public AppRate setMinDaysUntilPrompt(long minDaysUntilPrompt) { - this.minDaysUntilPrompt = minDaysUntilPrompt; - return this; - } - - /** - * @param showIfCrash If false the rate dialog will not be shown if the application has crashed once.
- * Default value is false. - * @return This {@link AppRate} object to allow chaining. - */ - public AppRate setShowIfAppHasCrashed(boolean showIfCrash) { - showIfHasCrashed = showIfCrash; - return this; - } - - /** - * Use this method if you want to customize the style and content of the rate dialog.
- * When using the {@link AlertDialog.Builder} you should use: - * - * @param customBuilder The custom dialog you want to use as the rate dialog. - * @return This {@link AppRate} object to allow chaining. - */ - public AppRate setCustomDialog(AlertDialog.Builder customBuilder) { - dialogBuilder = customBuilder; - return this; - } - - /** - * Reset all the data collected about number of launches and days until first launch. - * @param context A context. - */ - public static void reset(Context context) { - context.getSharedPreferences(PrefsContract.SHARED_PREFS_NAME, 0).edit().clear().commit(); - Log.d(TAG, "Cleared AppRate shared preferences."); - } - - /** - * Display the rate dialog if needed. - */ - public void init() { - - Log.d(TAG, "Init AppRate"); - - if (preferences.getBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, false) || ( - preferences.getBoolean(PrefsContract.PREF_APP_HAS_CRASHED, false) && !showIfHasCrashed)) { - return; - } - - if (!showIfHasCrashed) { - initExceptionHandler(); - } - - Editor editor = preferences.edit(); - - // Get and increment launch counter. - long launch_count = preferences.getLong(PrefsContract.PREF_LAUNCH_COUNT, 0) + 1; - editor.putLong(PrefsContract.PREF_LAUNCH_COUNT, launch_count); - - // Get date of first launch. - Long date_firstLaunch = preferences.getLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, 0); - if (date_firstLaunch == 0) { - date_firstLaunch = System.currentTimeMillis(); - editor.putLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, date_firstLaunch); - } - - // Show the rate dialog if needed. - if (launch_count >= minLaunchesUntilPrompt) { - if (System.currentTimeMillis() >= date_firstLaunch + (minDaysUntilPrompt * DateUtils.DAY_IN_MILLIS)) { - - if (dialogBuilder != null) { - showDialog(dialogBuilder); - } else { - showDefaultDialog(); - } - } - } - - editor.commit(); - } - - /** - * Initialize the {@link ExceptionHandler}. - */ - private void initExceptionHandler() { - - Log.d(TAG, "Init AppRate ExceptionHandler"); - - UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); - - // Don't register again if already registered. - if (!(currentHandler instanceof ExceptionHandler)) { - - // Register default exceptions handler. - Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(currentHandler, hostActivity)); - } - } - - /** - * Shows the default rate dialog. - * @return - */ - private void showDefaultDialog() { - - Log.d(TAG, "Create default dialog."); - - String title = "Rate " + getApplicationName(hostActivity.getApplicationContext()); - String message = "If you enjoy using " + getApplicationName(hostActivity.getApplicationContext()) + ", please take a moment to rate it. Thanks for your support!"; - String rate = "Rate it !"; - String remindLater = "Remind me later"; - String dismiss = "No thanks"; - - new AlertDialog.Builder(hostActivity) - .setTitle(title) - .setMessage(message) - .setPositiveButton(rate, this) - .setNegativeButton(dismiss, this) - .setNeutralButton(remindLater, this) - .setOnCancelListener(this) - .create().show(); - } - - /** - * Show the custom rate dialog. - * @return - */ - private void showDialog(AlertDialog.Builder builder) { - - Log.d(TAG, "Create custom dialog."); - - AlertDialog dialog = builder.create(); - dialog.show(); - - String rate = (String) dialog.getButton(AlertDialog.BUTTON_POSITIVE).getText(); - String remindLater = (String) dialog.getButton(AlertDialog.BUTTON_NEUTRAL).getText(); - String dismiss = (String) dialog.getButton(AlertDialog.BUTTON_NEGATIVE).getText(); - - dialog.setButton(AlertDialog.BUTTON_POSITIVE, rate, this); - dialog.setButton(AlertDialog.BUTTON_NEUTRAL, remindLater, this); - dialog.setButton(AlertDialog.BUTTON_NEGATIVE, dismiss, this); - - dialog.setOnCancelListener(this); - } - - @Override - public void onCancel(DialogInterface dialog) { - - Editor editor = preferences.edit(); - editor.putLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, System.currentTimeMillis()); - editor.putLong(PrefsContract.PREF_LAUNCH_COUNT, 0); - editor.commit(); - } - - /** - * @param onClickListener A listener to be called back on. - * @return This {@link AppRate} object to allow chaining. - */ - public AppRate setOnClickListener(OnClickListener onClickListener){ - clickListener = onClickListener; - return this; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - - Editor editor = preferences.edit(); - - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - try - { - hostActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + hostActivity.getPackageName()))); - }catch (ActivityNotFoundException e) { - Toast.makeText(hostActivity, "No Play Store installed on device", Toast.LENGTH_SHORT).show(); - } - editor.putBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, true); - break; - - case DialogInterface.BUTTON_NEGATIVE: - editor.putBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, true); - break; - - case DialogInterface.BUTTON_NEUTRAL: - editor.putLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, System.currentTimeMillis()); - editor.putLong(PrefsContract.PREF_LAUNCH_COUNT, 0); - break; - - default: - break; - } - - editor.commit(); - dialog.dismiss(); - - if(clickListener != null){ - clickListener.onClick(dialog, which); - } - } - - /** - * @param context A context of the current application. - * @return The application name of the current application. - */ - private static final String getApplicationName(Context context) { - final PackageManager packageManager = context.getPackageManager(); - ApplicationInfo applicationInfo; - try { - applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0); - } catch (final NameNotFoundException e) { - applicationInfo = null; - } - return (String) (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "(unknown)"); - } +import java.lang.Thread.UncaughtExceptionHandler; + +public class AppRate { + + private static final String TAG = "AppRater"; + + private Activity mHostActivity; + + private OnClickListener mClickListener; + + private OnClickListener mSendFeedbackClickListener; + + private OnClickListener mDoYouLikeAppClickListener; + + private SharedPreferences mPreferences; + + private AlertDialog.Builder mDialogBuilder = null; + + private AlertDialog.Builder mSendFeedbackDialogBuilder = null; + + private AlertDialog.Builder mDoYouLikeAppDialogBuilder = null; + + private long mMinLaunchesUntilPrompt = 0; + + private long mMinDaysUntilPrompt = 0; + + private boolean mShowIfHasCrashed = true; + + private boolean mResetOnAppUpgrade = false; + + private boolean mShowDoYouLikeTheAppFlow = false; + + private String mSendFeedbackEmailAddress; + + private String mSendFeedbackSubject; + + private String mSendFeedbackBody; + + private AppRaterEventListener mAppRaterEventListener; + + public AppRate(Activity hostActivity) { + mHostActivity = hostActivity; + mPreferences = hostActivity.getSharedPreferences(PrefsContract.SHARED_PREFS_NAME, Context.MODE_PRIVATE); + } + + /** + * @param minLaunchesUntilPrompt The minimum number of times the user lunches the application before showing the rate dialog.
+ * Default value is 0 times. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setMinLaunchesUntilPrompt(long minLaunchesUntilPrompt) { + mMinLaunchesUntilPrompt = minLaunchesUntilPrompt; + return this; + } + + /** + * @param minDaysUntilPrompt The minimum number of days before showing the rate dialog.
+ * Default value is 0 days. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setMinDaysUntilPrompt(long minDaysUntilPrompt) { + mMinDaysUntilPrompt = minDaysUntilPrompt; + return this; + } + + /** + * @param showIfCrash If false the rate dialog will not be shown if the application has crashed once.
+ * Default value is true. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setShowIfAppHasCrashed(boolean showIfCrash) { + mShowIfHasCrashed = showIfCrash; + return this; + } + + /** + * @param resetOnAppUpgrade If true the rate dialog tracking will be reset when the application is upgraded. + * This will allow the rate the application dialog to be shown again. + * Default value is false. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setResetOnAppUpgrade(boolean resetOnAppUpgrade) { + mResetOnAppUpgrade = resetOnAppUpgrade; + return this; + } + + /** + * Use this method if you want to customize the style and content of the rate dialog.
+ * When using the {@link AlertDialog.Builder} you should use: + * + * + * @param customBuilder The custom dialog you want to use as the rate dialog. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setCustomDialog(AlertDialog.Builder customBuilder) { + mDialogBuilder = customBuilder; + return this; + } + + /** + * Use this method if you want to customize the style and content of the do you like the app dialog.
+ * When using the {@link AlertDialog.Builder} you should use: + * + * + * @param customBuilder The custom dialog you want to use as the do you like the app dialog. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setCustomSendFeedbackDialog(AlertDialog.Builder customBuilder) { + mSendFeedbackDialogBuilder = customBuilder; + return this; + } + + /** + * Use this method if you want to customize the style and content of the send feedback dialog.
+ * When using the {@link AlertDialog.Builder} you should use: + * + * + * @param customBuilder The custom dialog you want to use as the send feedback dialog. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setCustomDoYouLikeAppDialog(AlertDialog.Builder customBuilder) { + mDoYouLikeAppDialogBuilder = customBuilder; + return this; + } + + /** + * Enable do you like the app flow instead of showing just the rating dialog. + * The flow will be as follows: + * + *

+ * + * @param sendFeedbackEmailAddress The email address that the user will send feedback to + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate showDoYouLikeTheAppFlow(String sendFeedbackEmailAddress) { + mShowDoYouLikeTheAppFlow = true; + mSendFeedbackEmailAddress = sendFeedbackEmailAddress; + return this; + } + + /** + * Set the subject for the send feedback dialog. + * + * @param subject + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setSendFeedbackSubject(String subject) { + mSendFeedbackSubject = subject; + return this; + } + + /** + * Set the body for the send feedback dialog. + * + * @param body + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setSendFeedbackBody(String body) { + mSendFeedbackBody = body; + return this; + } + + /** + * Reset all the data collected about number of launches and days until first launch. + * + * @param context A context. + */ + public static void reset(Context context) { + context.getSharedPreferences(PrefsContract.SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit().clear().commit(); + Log.d(TAG, "Cleared AppRate shared preferences."); + } + + /** + * Display the rate dialog if needed. + */ + public void init() { + Log.d(TAG, "Init AppRate"); + + Editor editor = mPreferences.edit(); + performAppUpgradeCheck(editor); + + if (!mShowIfHasCrashed) { + initExceptionHandler(); + } + + if (mPreferences.getBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, false) || ( + mPreferences.getBoolean(PrefsContract.PREF_APP_HAS_CRASHED, false) && !mShowIfHasCrashed)) { + return; + } + + // Get and increment launch counter. + long launch_count = mPreferences.getLong(PrefsContract.PREF_LAUNCH_COUNT, 0) + 1; + editor.putLong(PrefsContract.PREF_LAUNCH_COUNT, launch_count); + + // Get date of first launch. + Long date_firstLaunch = mPreferences.getLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, 0); + if (date_firstLaunch == 0) { + date_firstLaunch = System.currentTimeMillis(); + editor.putLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, date_firstLaunch); + } + + // Show the first dialog if needed. + if (launch_count >= mMinLaunchesUntilPrompt) { + if (System.currentTimeMillis() >= date_firstLaunch + (mMinDaysUntilPrompt * DateUtils.DAY_IN_MILLIS)) { + if (mShowDoYouLikeTheAppFlow) { + showDoYouLikeAppDialog(); + } else { + showDialog(); + } + + // Notify listener that we have shown a dialog starting the flow + if (mAppRaterEventListener != null) { + mAppRaterEventListener.onAppRaterDialogsShown(); + } + } + } + + editor.commit(); + } + + /** + * Checks and saves off the current version information for the application. If the application has been upgraded, and configured to do so, then + * reset tracking information to allow the rate dialog to be shown again. + */ + private void performAppUpgradeCheck(Editor editor) { + Integer currentAppVersionCode = AppInfo.getApplicationVersionCode(mHostActivity.getApplicationContext()); + Integer lastRunAppVersionCode = mPreferences.getInt(PrefsContract.PREF_APP_VERSION_CODE, -1); + + // If the version has been initialized, we are being upgraded, and the user enabled resetting on upgrading + if (lastRunAppVersionCode != -1 && currentAppVersionCode > lastRunAppVersionCode && mResetOnAppUpgrade) { + AppRate.reset(mHostActivity); + } + + // Save off the current app version code + editor.putInt(PrefsContract.PREF_APP_VERSION_CODE, currentAppVersionCode); + editor.commit(); + } + + /** + * Initialize the {@link ExceptionHandler}. + */ + private void initExceptionHandler() { + Log.d(TAG, "Init AppRate ExceptionHandler"); + + UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + + // Don't register again if already registered. + if (!(currentHandler instanceof ExceptionHandler)) { + // Register default exceptions handler. + Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(currentHandler, mHostActivity)); + } + } + + /** + * Shows the rate dialog using either the default or user provided builder. + */ + private void showDialog() { + if (mDialogBuilder != null) { + showCustomDialog(mDialogBuilder); + } else { + showDefaultDialog(); + } + } + + /** + * Shows the do you like the app dialog using either the default or user provided builder. + */ + private void showDoYouLikeAppDialog() { + if (mDoYouLikeAppDialogBuilder != null) { + showCustomDoYouLikeAppDialog(mDoYouLikeAppDialogBuilder); + } else { + showDefaultDoYouLikeAppDialog(); + } + } + + /** + * Shows the send feedback dialog using either the default or user provided builder. + */ + private void showSendFeedbackDialog() { + if (mSendFeedbackDialogBuilder != null) { + showCustomSendFeedbackDialog(mSendFeedbackDialogBuilder); + } else { + showDefaultSendFeedbackDialog(); + } + } + + /** + * Shows the default rate dialog. + * + * @return + */ + private void showDefaultDialog() { + Log.d(TAG, "Create default dialog."); + + String title = mHostActivity.getString(R.string.dialog_title, AppInfo.getApplicationName(mHostActivity.getApplicationContext())); + String message = mHostActivity.getString(R.string.dialog_message, AppInfo.getApplicationName(mHostActivity.getApplicationContext())); + String positiveButtonText = mHostActivity.getString(R.string.dialog_positive_button); + String neutralButtonText = mHostActivity.getString(R.string.dialog_neutral_button); + String negativeButtonText = mHostActivity.getString(R.string.dialog_negative_button); + + new AlertDialog.Builder(mHostActivity) + .setTitle(title) + .setMessage(message) + .setPositiveButton(positiveButtonText, mDialogOnClickListener) + .setNegativeButton(negativeButtonText, mDialogOnClickListener) + .setNeutralButton(neutralButtonText, mDialogOnClickListener) + .setOnCancelListener(mDialogOnCancelListener) + .create().show(); + } + + /** + * Shows the default do you like app dialog. + * + * @return + */ + private void showDefaultDoYouLikeAppDialog() { + Log.d(TAG, "Create default do you like app dialog."); + + String title = mHostActivity.getString(R.string.like_app_dialog_title, AppInfo.getApplicationName(mHostActivity.getApplicationContext())); + String message = mHostActivity.getString(R.string.like_app_dialog_message, AppInfo.getApplicationName(mHostActivity.getApplicationContext())); + String positiveButtonText = mHostActivity.getString(R.string.like_app_dialog_positive_button); + String negativeButtonText = mHostActivity.getString(R.string.like_app_dialog_negative_button); + + new AlertDialog.Builder(mHostActivity) + .setTitle(title) + .setMessage(message) + .setPositiveButton(positiveButtonText, mDoYouLikeAppDialogOnClickListener) + .setNegativeButton(negativeButtonText, mDoYouLikeAppDialogOnClickListener) + .setOnCancelListener(mDoYouLikeAppDialogOnCancelListener) + .create().show(); + } + + /** + * Shows the default send feedback dialog. + * + * @return + */ + private void showDefaultSendFeedbackDialog() { + Log.d(TAG, "Create default send feedback dialog."); + + String title = mHostActivity.getString(R.string.send_feedback_dialog_title, AppInfo.getApplicationName(mHostActivity.getApplicationContext())); + String message = mHostActivity.getString(R.string.send_feedback_dialog_message, AppInfo.getApplicationName(mHostActivity.getApplicationContext())); + String positiveButtonText = mHostActivity.getString(R.string.send_feedback_dialog_positive_button); + String negativeButtonText = mHostActivity.getString(R.string.send_feedback_dialog_negative_button); + + new AlertDialog.Builder(mHostActivity) + .setTitle(title) + .setMessage(message) + .setPositiveButton(positiveButtonText, mSendFeedbackDialogOnClickListener) + .setNegativeButton(negativeButtonText, mSendFeedbackDialogOnClickListener) + .setOnCancelListener(mSendFeedbackDialogOnCancelListener) + .create().show(); + } + + /** + * Show the custom rate dialog. + * + * @return + */ + private void showCustomDialog(AlertDialog.Builder builder) { + Log.d(TAG, "Create custom dialog."); + buildDialogWithWrapperedClickHandlers(builder, mDialogOnClickListener, mDialogOnCancelListener); + } + + /** + * Show the custom do you like app dialog. + * + * @return + */ + private void showCustomDoYouLikeAppDialog(AlertDialog.Builder builder) { + Log.d(TAG, "Create custom do you like app dialog."); + buildDialogWithWrapperedClickHandlers(builder, mDoYouLikeAppDialogOnClickListener, mDoYouLikeAppDialogOnCancelListener); + } + + /** + * Show the custom send feedback dialog. + * + * @return + */ + private void showCustomSendFeedbackDialog(AlertDialog.Builder builder) { + Log.d(TAG, "Create custom send feedback dialog."); + buildDialogWithWrapperedClickHandlers(builder, mSendFeedbackDialogOnClickListener, mSendFeedbackDialogOnCancelListener); + } + + /** + * Create the dialog using the provided builder. After created reassign all on click listeners to route through the provided listener. + * + * @param builder + * @param onClickListener + * @param onCancelListener + */ + private void buildDialogWithWrapperedClickHandlers(AlertDialog.Builder builder, OnClickListener onClickListener, OnCancelListener onCancelListener) { + // Make the dialog + AlertDialog dialog = builder.create(); + dialog.show(); + + // Wrapper each of the button click listeners with our own that gets called first + updateDialogButtonClickListeners(dialog, AlertDialog.BUTTON_POSITIVE, onClickListener); + updateDialogButtonClickListeners(dialog, AlertDialog.BUTTON_NEUTRAL, onClickListener); + updateDialogButtonClickListeners(dialog, AlertDialog.BUTTON_NEGATIVE, onClickListener); + + // Wrapper the on cancel listener + dialog.setOnCancelListener(onCancelListener); + } + + /** + * Update all of the dialogs buttons to use the provided onClickListener + * + * @param dialog + * @param whichButton + * @param onClickListener + */ + private void updateDialogButtonClickListeners(AlertDialog dialog, int whichButton, OnClickListener onClickListener) { + if (dialog != null) { + Button button = dialog.getButton(whichButton); + if (button != null) { + String buttonText = (String) button.getText(); + dialog.setButton(whichButton, buttonText, onClickListener); + } + } + } + + /** + * @param onClickListener A listener to be called back on click actions to the rate dialog. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setOnClickListener(OnClickListener onClickListener) { + mClickListener = onClickListener; + return this; + } + + /** + * @param onClickListener A listener to be called back on click actions to the do you like the app dialog. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setDoYouLikeAppOnClickListener(OnClickListener onClickListener) { + mDoYouLikeAppClickListener = onClickListener; + return this; + } + + /** + * @param onClickListener A listener to be called back on click actions to the send feedback dialog. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setSendFeedbackOnClickListener(OnClickListener onClickListener) { + mSendFeedbackClickListener = onClickListener; + return this; + } + + public interface AppRaterEventListener { + public void onAppRaterDialogsShown(); + } + + /** + * @param appRaterEventListener listener to be called back when the app rater launches its first dialog to begin a flow. + * @return This {@link AppRate} object to allow chaining. + */ + public AppRate setAppRaterEventListener(AppRaterEventListener appRaterEventListener) { + mAppRaterEventListener = appRaterEventListener; + return this; + } + + private OnClickListener mDialogOnClickListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Editor editor = mPreferences.edit(); + + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + try { + mHostActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + mHostActivity.getPackageName()))); + } catch (ActivityNotFoundException e) { + Toast.makeText(mHostActivity, mHostActivity.getString(R.string.toast_play_store_missing_error), Toast.LENGTH_SHORT).show(); + } + editor.putBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, true); + break; + + case DialogInterface.BUTTON_NEGATIVE: + editor.putBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, true); + break; + + case DialogInterface.BUTTON_NEUTRAL: + editor.putLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, System.currentTimeMillis()); + editor.putLong(PrefsContract.PREF_LAUNCH_COUNT, 0); + break; + + default: + break; + } + + editor.commit(); + dialog.dismiss(); + + if (mClickListener != null) { + mClickListener.onClick(dialog, which); + } + } + }; + + private OnClickListener mDoYouLikeAppDialogOnClickListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Dismiss the do you like the app dialog + dialog.dismiss(); + + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + // Show the rating dialog + showDialog(); + break; + + case DialogInterface.BUTTON_NEGATIVE: + // They don't like the app; don't prompt them again + doNotShowDialogAgain(); + + // Show the send feedback dialog + showSendFeedbackDialog(); + break; + + default: + break; + } + + if (mDoYouLikeAppClickListener != null) { + mDoYouLikeAppClickListener.onClick(dialog, which); + } + } + }; + + private OnClickListener mSendFeedbackDialogOnClickListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Editor editor = mPreferences.edit(); + + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + SendFeedback sendFeedback = new SendFeedback(mHostActivity); + sendFeedback.promptForFeedback(mSendFeedbackEmailAddress, mSendFeedbackSubject, mSendFeedbackBody); + break; + + case DialogInterface.BUTTON_NEGATIVE: + // Nothing to do + break; + + default: + break; + } + + editor.commit(); + dialog.dismiss(); + + if (mSendFeedbackClickListener != null) { + mSendFeedbackClickListener.onClick(dialog, which); + } + } + }; + + private OnCancelListener mDialogOnCancelListener = new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + resetLaunchData(); + } + }; + + private void resetLaunchData() { + Editor editor = mPreferences.edit(); + editor.putLong(PrefsContract.PREF_DATE_FIRST_LAUNCH, System.currentTimeMillis()); + editor.putLong(PrefsContract.PREF_LAUNCH_COUNT, 0); + editor.commit(); + } + + private void doNotShowDialogAgain() { + Editor editor = mPreferences.edit(); + editor.putBoolean(PrefsContract.PREF_DONT_SHOW_AGAIN, true); + editor.commit(); + } + + private OnCancelListener mDoYouLikeAppDialogOnCancelListener = new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + resetLaunchData(); + } + }; + + private OnCancelListener mSendFeedbackDialogOnCancelListener = new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + doNotShowDialogAgain(); + } + }; } \ No newline at end of file diff --git a/AppRate/src/com/tjeannin/apprate/ExceptionHandler.java b/AppRate/src/com/tjeannin/apprate/ExceptionHandler.java index 6e97ff8..c27c536 100644 --- a/AppRate/src/com/tjeannin/apprate/ExceptionHandler.java +++ b/AppRate/src/com/tjeannin/apprate/ExceptionHandler.java @@ -1,27 +1,26 @@ package com.tjeannin.apprate; -import java.lang.Thread.UncaughtExceptionHandler; - import android.content.Context; import android.content.SharedPreferences; +import java.lang.Thread.UncaughtExceptionHandler; + public class ExceptionHandler implements UncaughtExceptionHandler { - private UncaughtExceptionHandler defaultExceptionHandler; - SharedPreferences preferences; + private UncaughtExceptionHandler mDefaultExceptionHandler; - // Constructor. - public ExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler, Context context) - { - preferences = context.getSharedPreferences(PrefsContract.SHARED_PREFS_NAME, 0); - defaultExceptionHandler = uncaughtExceptionHandler; - } + private SharedPreferences mPreferences; - public void uncaughtException(Thread thread, Throwable throwable) { + // Constructor. + public ExceptionHandler(UncaughtExceptionHandler uncaughtExceptionHandler, Context context) { + mPreferences = context.getSharedPreferences(PrefsContract.SHARED_PREFS_NAME, Context.MODE_PRIVATE); + mDefaultExceptionHandler = uncaughtExceptionHandler; + } - preferences.edit().putBoolean(PrefsContract.PREF_APP_HAS_CRASHED, true).commit(); + public void uncaughtException(Thread thread, Throwable throwable) { + mPreferences.edit().putBoolean(PrefsContract.PREF_APP_HAS_CRASHED, true).commit(); - // Call the original handler. - defaultExceptionHandler.uncaughtException(thread, throwable); - } + // Call the original handler. + mDefaultExceptionHandler.uncaughtException(thread, throwable); + } } \ No newline at end of file diff --git a/AppRate/src/com/tjeannin/apprate/PrefsContract.java b/AppRate/src/com/tjeannin/apprate/PrefsContract.java index 4f75b1c..001cef1 100644 --- a/AppRate/src/com/tjeannin/apprate/PrefsContract.java +++ b/AppRate/src/com/tjeannin/apprate/PrefsContract.java @@ -9,4 +9,5 @@ public class PrefsContract { public static final String PREF_LAUNCH_COUNT = "launch_count"; public static final String PREF_DONT_SHOW_AGAIN = "dont_show_again"; public static final String PREF_DONT_SHOW_IF_CRASHED = "pref_dont_show_if_crashed"; + public static final String PREF_APP_VERSION_CODE = "pref_app_version_code"; } diff --git a/AppRate/src/com/tjeannin/apprate/SendFeedback.java b/AppRate/src/com/tjeannin/apprate/SendFeedback.java new file mode 100644 index 0000000..f27b9d7 --- /dev/null +++ b/AppRate/src/com/tjeannin/apprate/SendFeedback.java @@ -0,0 +1,71 @@ +package com.tjeannin.apprate; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; + +/** + * This class will craft a feedback email to allow the user to send feedback. + */ +public class SendFeedback { + private Activity mActivity; + + public SendFeedback(Activity activity) { + mActivity = activity; + } + + /** + * Prompt the user for feedback by generating and sending an intent to open their email client. + * + * @param feedbackEmailAddress + * @param subject (Optional) If null default subject will be used + * @param body (Optional) If null the default body will be used + */ + public void promptForFeedback(String feedbackEmailAddress, String subject, String body) { + if (mActivity == null) return; + + // Generate default body text + StringBuilder defaultBodyText = new StringBuilder(); + defaultBodyText.append("\n\n\n\n"); + defaultBodyText.append("------------------------------------\n\n"); + defaultBodyText.append(mActivity.getString(R.string.feedback_email_header)); + defaultBodyText.append("\n\n"); + defaultBodyText.append("\n" + mActivity.getString(R.string.email_heading_android_device, getDeviceName())); + defaultBodyText.append("\n" + mActivity.getString(R.string.email_heading_android_version, Build.VERSION.RELEASE)); + defaultBodyText.append("\n" + mActivity.getString(R.string.email_heading_app_version, AppInfo.getApplicationVersionName(mActivity))); + + String defaultSubjectText = mActivity.getString(R.string.feedback_email_subject_line, AppInfo.getApplicationName(mActivity)); + + Intent i = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", feedbackEmailAddress, null)); + i.putExtra(Intent.EXTRA_SUBJECT, subject != null ? subject : defaultSubjectText); + i.putExtra(Intent.EXTRA_TEXT, body != null ? body : defaultBodyText.toString()); + + try { + mActivity.startActivity(Intent.createChooser(i, mActivity.getString(R.string.send_email))); + } catch (android.content.ActivityNotFoundException ex) { + } + } + + public String getDeviceName() { + String manufacturer = Build.MANUFACTURER; + String model = Build.MODEL; + if (model.startsWith(manufacturer)) { + return capitalize(model); + } else { + return capitalize(manufacturer) + " " + model; + } + } + + private String capitalize(String s) { + if (s == null || s.length() == 0) { + return ""; + } + char first = s.charAt(0); + if (Character.isUpperCase(first)) { + return s; + } else { + return Character.toUpperCase(first) + s.substring(1); + } + } +} diff --git a/AppRateSample/.classpath b/AppRateSample/.classpath index a662f00..d57ec02 100644 --- a/AppRateSample/.classpath +++ b/AppRateSample/.classpath @@ -1,8 +1,9 @@ - - - - - - - - + + + + + + + + + diff --git a/AppRateSample/AndroidManifest.xml b/AppRateSample/AndroidManifest.xml index be8a123..9210e61 100644 --- a/AppRateSample/AndroidManifest.xml +++ b/AppRateSample/AndroidManifest.xml @@ -5,7 +5,7 @@ + android:targetSdkVersion="17" /> diff --git a/AppRateSample/build.gradle b/AppRateSample/build.gradle new file mode 100644 index 0000000..202406d --- /dev/null +++ b/AppRateSample/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'android' + +dependencies { + compile fileTree(dir: 'libs', include: '*.jar') + compile project(':AppRate') +} + +android { + compileSdkVersion 16 + buildToolsVersion "18.1.0" + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + + // Move the tests to tests/java, tests/res, etc... + instrumentTest.setRoot('tests') + + // Move the build types to build-types/ + // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ... + // This moves them out of them default location under src//... which would + // conflict with src/ being used by the main source set. + // Adding new build types or product flavors should be accompanied + // by a similar customization. + debug.setRoot('build-types/debug') + release.setRoot('build-types/release') + } +} diff --git a/AppRateScreenshots/screenshot_doyoulikeapp_dark.png b/AppRateScreenshots/screenshot_doyoulikeapp_dark.png new file mode 100644 index 0000000..8f1e210 Binary files /dev/null and b/AppRateScreenshots/screenshot_doyoulikeapp_dark.png differ diff --git a/AppRateScreenshots/screenshot_doyoulikeapp_light.png b/AppRateScreenshots/screenshot_doyoulikeapp_light.png new file mode 100644 index 0000000..fa38c50 Binary files /dev/null and b/AppRateScreenshots/screenshot_doyoulikeapp_light.png differ diff --git a/AppRateScreenshots/screenshot_sendfeedback_dark.png b/AppRateScreenshots/screenshot_sendfeedback_dark.png new file mode 100644 index 0000000..38c7844 Binary files /dev/null and b/AppRateScreenshots/screenshot_sendfeedback_dark.png differ diff --git a/AppRateScreenshots/screenshot_sendfeedback_light.png b/AppRateScreenshots/screenshot_sendfeedback_light.png new file mode 100644 index 0000000..43c35c8 Binary files /dev/null and b/AppRateScreenshots/screenshot_sendfeedback_light.png differ diff --git a/README.md b/README.md index 7990f20..75cbb87 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,24 @@ AppRate ======= -* AppRate allows your users to rate your application. +* AppRate allows your users to rate your application and will optionally prompt for email feedback if they don't like your application. * AppRate shows a customizable rate dialog according to your chosen settings. +* If enabled, then the user will first be asked if they like the application prior to asking them to rate it. If they respond that they don't like the application they will be prompted to leave email feedback. Both of these dialogs can be customized. + +Screenshots +----------- +![DoYouLikeApp Dark](AppRateScreenshots/screenshot_doyoulikeapp_dark.png "Do you like the app (dark)") +![DoYouLikeApp Light](AppRateScreenshots/screenshot_doyoulikeapp_light.png "Do you like the app (light)") + +![Screenshot 1](AppRateScreenshots/screenshot_1.png "Screenshot 1") +![Screenshot 2](AppRateScreenshots/screenshot_2.png "Screenshot 2") + +![Send Feedback Dark](AppRateScreenshots/screenshot_sendfeedback_dark.png "Send Feedback (dark)") +![Send Feedback Light](AppRateScreenshots/screenshot_sendfeedback_light.png "Send Feedback (light)") + + How to install and use ---------------------- @@ -29,6 +43,14 @@ new AppRate(this) .init(); ``` +* You can decide **to reprompt the user** if the application **has been upgraded**. + +```java +new AppRate(this) + .setResetOnAppUpgrade(true) + .init(); +``` + * You can decide **when to prompt the user**. ```java @@ -38,6 +60,32 @@ new AppRate(this) .init(); ``` +* You can decide to **ask the user** if they **like the application first**. + +```java +new AppRate(this) + .showDoYouLikeTheAppFlow("support@your_support_email_address.com") + .init(); +``` + +* You can **customize** the send feedback **email subject**. + +```java +new AppRate(this) + .showDoYouLikeTheAppFlow("support@your_support_email_address.com") + .setSendFeedbackSubject("Subject") + .init(); +``` + +* You can **customize** the send feedback **email body**. + +```java +new AppRate(this) + .showDoYouLikeTheAppFlow("support@your_support_email_address.com") + .setSendFeedbackBody("Body") + .init(); +``` + * You can **customize** all the messages and buttons of **the rate dialog**. ```java @@ -54,24 +102,102 @@ new AppRate(this) .init(); ``` -* You can set **your own click listener**. +* You can set **your own click listener** on **the rate dialog**. ```java new AppRate(this) .setOnClickListener(new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - // Do something. + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + Log.v(TAG, "Rate dialog positive button clicked"); + break; + case DialogInterface.BUTTON_NEGATIVE: + Log.v(TAG, "Rate dialog negative button clicked"); + break; + case DialogInterface.BUTTON_NEUTRAL: + Log.v(TAG, "Rate dialog neutral button clicked"); + break; + default: + break; + } } }) .init(); ``` -Screenshots ------------ +* You can **customize** all the messages and buttons of **the do you like the app dialog**. -![Screenshot 1](AppRateScreenshots/screenshot_1.png "Screenshot 1") -![Screenshot 2](AppRateScreenshots/screenshot_2.png "Screenshot 2") +```java +AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle("Like Us?") + .setMessage("Do you totally dig us?") + .setPositiveButton("Heck Yes", null) + .setNegativeButton("No!!!", null); + +new AppRate(this) + .setCustomDoYouLikeAppDialog(builder) + .init(); +``` + +* You can set **your own click listener** on **the do you like the app dialog**. + +```java +new AppRate(this) + .setDoYouLikeAppOnClickListener(new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + Log.v(TAG, "Do you like the app dialog positive button clicked"); + break; + case DialogInterface.BUTTON_NEGATIVE: + Log.v(TAG, "Do you like the app dialog negative button clicked"); + break; + default: + break; + } + } + }) + .init(); +``` + +* You can **customize** all the messages and buttons of **the send feedback dialog**. + +```java +AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle("Help us out") + .setMessage("Want to tell us what you don't like?") + .setPositiveButton("Okay", null) + .setNegativeButton("No", null); + +new AppRate(this) + .setCustomSendFeedbackDialog(builder) + .init(); +``` + +* You can set **your own click listener** on **the do you like the app dialog**. + +```java +new AppRate(this) + .setSendFeedbackOnClickListener(new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + Log.v(TAG, "Send feedback dialog positive button clicked"); + break; + case DialogInterface.BUTTON_NEGATIVE: + Log.v(TAG, "Send feedback dialog negative button clicked"); + break; + default: + break; + } + } + }) + .init(); +``` License ------- diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..dcaed5d --- /dev/null +++ b/build.gradle @@ -0,0 +1,13 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:0.7.+' + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.9' +} \ 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..d5c591c 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..2e022cf --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Dec 31 15:34:58 EST 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip diff --git a/gradlew b/gradlew new file mode 100755 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..aec9973 --- /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/settings.gradle b/settings.gradle new file mode 100644 index 0000000..59ff32c --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':AppRate' +include ':AppRateSample'